Débuter avec Elixir
Pourquoi Elixir ?
Elixir est un langage de programmation fonctionnel concurrentiel qui utilise la machine virtuelle Erlang (VM) pour son exécution. Il offre une gamme complète d'outils, des bibliothèques et des frameworks pour aider les développeurs à créer des applications performantes, scalables et fiables.
Dans un contexte réel, imaginez que vous travaillez sur une application de gestion de projet. Vous avez besoin d'un système qui peut gérer les tâches en parallèle, éviter les erreurs de concurrence et s'adapter à la croissance du projet. Elixir offre toutes ces caractéristiques avec sa philosophie fonctionnelle et son modèle concurrentiel.
Prérequis
- Connaissances en programmation : Aucune connaissance spécifique n'est requise, mais une compréhension de base des concepts de programmation est utile.
- Outils à installer :
- Elixir : La version recommandée est la dernière stable. Vous pouvez l'installer via
asdfou directement avec le script d'installation officiel.# Installez asdf (gestionnaire de versions) git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.8.1 # Ajoutez Elixir à asdf echo -e '\n. $HOME/.asdf/asdf.sh' >> ~/.bashrc echo -e '. $HOME/.asdf/completions/asdf.bash' >> ~/.bashrc source ~/.bashrc # Installez Elixir avec asdf asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git asdf install elixir 1.14.0 - IEx : L'Interpréteur Elixir est fourni avec l'installation d'Elixir.
- Emacs, VSCode ou VIM : Un éditeur de code moderne pour une meilleure productivité.
- Elixir : La version recommandée est la dernière stable. Vous pouvez l'installer via
Concepts fondamentaux
Atoms
Un atom est une chaîne de caractères immuable et unique. Ils sont souvent utilisés comme des noms de variables, des constantes et des étiquettes.
## Créer un atom
my_atom = :mon_atom
## Comparer deux atoms
IO.puts my_atom == :mon_atom # true
Listes
Les listes en Elixir sont des structures de données immuables. Elles sont formées d'une série d'éléments encadrés par crochets.
## Créer une liste
my_list = [1, 2, 3]
## Ajouter un élément à la fin de la liste
new_list = my_list ++ [4] # [1, 2, 3, 4]
Tuples
Les tuples en Elixir sont des structures de données immuables similaires aux listes, mais avec une syntaxe différente. Ils sont utiles pour stocker un ensemble fixe d'éléments.
## Créer un tuple
my_tuple = {1, 2, 3}
## Accéder à un élément du tuple
IO.puts elem(my_tuple, 0) # 1
Fonctions
Les fonctions en Elixir sont définies avec le mot-clé def. Elles peuvent prendre des arguments et retourner une valeur.
## Définir une fonction
defmodule Math do
def add(a, b) do
a + b
end
end
## Appeler la fonction
result = Math.add(2, 3)
IO.puts result # 5
Modules
Les modules en Elixir permettent de regrouper des fonctions et des variables sous un nom unique. Ils sont essentiels pour structurer le code.
## Définir un module
defmodule Greeting do
def hello(name) do
"Hello, #{name}!"
end
end
## Appeler une fonction du module
greet = Greeting.hello("Alice")
IO.puts greet # Hello, Alice!
Compréhensions
Les compréhensions en Elixir permettent de créer des listes et des tuples de manière concise.
## Créer une liste avec une compréhension
squares = for x <- 1..5 do
x * x
end
IO.inspect squares # [1, 4, 9, 16, 25]
Mise en pratique : Projet fil rouge
Nous allons construire un simple gestionnaire de tâches. Le projet comprendra les fonctionnalités suivantes :
- Ajouter une tâche
- Marquer une tâche comme terminée
- Afficher la liste des tâches
Étape 1 : Création du projet
## Initialiser un nouveau projet Elixir
mix new task_manager
cd task_manager
Étape 2 : Ajout de tâches
Créons une module TaskManager pour gérer les tâches.
## lib/task_manager.ex
defmodule TaskManager do
defstruct [:tasks]
def new() do
%__MODULE__{tasks: []}
end
def add_task(%__MODULE__{} = state, task) do
%{state | tasks: [task | state.tasks]}
end
end
Étape 3 : Marquer une tâche comme terminée
Ajoutons une fonction pour marquer une tâche comme terminée.
## lib/task_manager.ex (ajoutez cette fonction)
def mark_task_as_done(%__MODULE__{} = state, index) do
{task, new_tasks} = Enum.pop_at(state.tasks, index)
%{state | tasks: List.replace_at(new_tasks, index, "#{task} (done)" )}
end
Étape 4 : Afficher la liste des tâches
Ajoutons une fonction pour afficher la liste des tâches.
## lib/task_manager.ex (ajoutez cette fonction)
def show_tasks(%__MODULE__{} = state) do
Enum.each(state.tasks, fn task -> IO.puts task end)
end
Étape 5 : Tester le gestionnaire de tâches
Créons un fichier script.exs pour tester notre gestionnaire de tâches.
## script.exs
{:ok, pid} = Task.Supervisor.start_link()
{:ok, manager} = TaskManager.new()
manager = TaskManager.add_task(manager, "Task 1")
manager = TaskManager.add_task(manager, "Task 2")
IO.puts "\nTasks:"
TaskManager.show_tasks(manager)
manager = TaskManager.mark_task_as_done(manager, 0)
IO.puts "\nUpdated Tasks:"
TaskManager.show_tasks(manager)
Exécutons le script :
elixir script.exs
Erreurs fréquentes et debugging
Erreur 1 : Erreur de syntaxe
Message d'erreur :
syntax error before: 'end'
Code incorrect :
defmodule TaskManager do
def new() do
%__MODULE__{tasks: []}
end
# Manque l'indentation correcte
def add_task(%__MODULE__{} = state, task) do
%{state | tasks: [task | state.tasks]}
end
end
Code correct :
defmodule TaskManager do
def new() do
%__MODULE__{tasks: []}
end
# Ajoutez l'indentation correcte
def add_task(%__MODULE__{} = state, task) do
%{state | tasks: [task | state.tasks]}
end
end
Erreur 2 : Erreur de pattern matching
Message d'erreur :
** (MatchError) no match of right hand side value: nil
Code incorrect :
defmodule TaskManager do
def mark_task_as_done(%__MODULE__{} = state, index) do
{task, new_tasks} = Enum.pop_at(state.tasks, index)
%{state | tasks: List.replace_at(new_tasks, index, "#{task} (done)" )}
end
end
Code correct :
defmodule TaskManager do
def mark_task_as_done(%__MODULE__{} = state, index) when is_integer(index) and index >= 0 do
{task, new_tasks} = Enum.pop_at(state.tasks, index)
%{state | tasks: List.replace_at(new_tasks, index, "#{task} (done)" )}
end
def mark_task_as_done(%__MODULE__{} = state, _index) do
IO.puts "Invalid index"
state
end
end
Erreur 3 : Erreur de module non trouvé
Message d'erreur :
** (UndefinedFunctionError) function TaskManager.show_tasks/1 is undefined or private. Did you mean one of:
* TaskManager.add_task/2
(task_manager 0.1.0) TaskManager.show_tasks(nil)
Code incorrect :
## script.exs
{:ok, pid} = Task.Supervisor.start_link()
{:ok, manager} = TaskManager.new()
manager = TaskManager.add_task(manager, "Task 1")
manager = TaskManager.add_task(manager, "Task 2")
IO.puts "\nTasks:"
TaskManager.show_tasks() # Erreur : manque l'argument
Code correct :
## script.exs
{:ok, pid} = Task.Supervisor.start_link()
{:ok, manager} = TaskManager.new()
manager = TaskManager.add_task(manager, "Task 1")
manager = TaskManager.add_task(manager, "Task 2")
IO.puts "\nTasks:"
TaskManager.show_tasks(manager) # Ajoutez l'argument
Pour aller plus loin
Piste 1 : Utiliser le pattern matching
Explorez comment utiliser le pattern matching pour gérer des cas spécifiques. Par exemple, vous pouvez modifier la fonction add_task pour permettre d'ajouter plusieurs tâches à la fois.
defmodule TaskManager do
def add_task(%__MODULE__{} = state, tasks) when is_list(tasks) do
Enum.reduce(tasks, state, fn task, acc -> add_task(acc, task) end)
end
# La fonction existante reste inchangée
end
Piste 2 : Créer une interface utilisateur avec IEx
Utilisez IEx pour créer une simple interface utilisateur qui permet d'ajouter et de gérer les tâches.
defmodule TaskManager do
def interactively_add_task(%__MODULE__{} = state) do
IO.puts "Enter task (or 'exit' to quit):"
input = IO.gets("> ")
if input == "exit\n" do
state
else
new_state = add_task(state, String.trim(input))
interactively_add_task(new_state)
end
end
end
Piste 3 : Tester avec ExUnit
Ajoutez des tests pour votre gestionnaire de tâches en utilisant ExUnit.
## test/task_manager_test.exs
defmodule TaskManagerTest do
use ExUnit.Case, async: true
alias TaskManager
test "adding a task" do
manager = TaskManager.new()
manager = TaskManager.add_task(manager, "Task 1")
assert manager.tasks == ["Task 1"]
end
test "marking a task as done" do
manager = TaskManager.new()
manager = TaskManager.add_task(manager, "Task 1")
manager = TaskManager.mark_task_as_done(manager, 0)
assert manager.tasks == ["Task 1 (done)"]
end
end
Défi pratique
Créez une application simple en utilisant Elixir pour gérer des notes. L'application devrait permettre d'ajouter des notes, de les marquer comme faites et de les afficher.