Retour vers le sommaire des tips Accueil
Bases 2/4 — Pipe |>, with et récursion
🟢 Niveau débutant · Livebook exécutable (aucune dépendance). Objectif : écrire des enchaînements lisibles et savoir « boucler » sans boucle. Prérequis : Immutabilité & pattern matching.
Le pipe |> : lire de gauche à droite
Le pipe passe le résultat de gauche comme premier argument de la fonction de droite. On lit le traitement dans l’ordre où il se déroule, au lieu de l’imbriquer à l’envers.
# Sans pipe : à lire de l'intérieur vers l'extérieur 😵
Enum.sum(Enum.filter(Enum.map(1..10, fn x -> x * x end), fn x -> rem(x, 2) == 0 end))
# Avec pipe : ça se lit comme une recette 👨🍳
1..10
|> Enum.map(fn x -> x * x end)
|> Enum.filter(fn x -> rem(x, 2) == 0 end)
|> Enum.sum()
Règle d’or : la valeur qui circule doit être le premier argument. Beaucoup de fonctions de la stdlib sont conçues ainsi exprès (la donnée d’abord).
" Bonjour le monde "
|> String.trim()
|> String.downcase()
|> String.split(" ")
with : enchaîner des étapes qui peuvent échouer
Quand plusieurs opérations renvoient {:ok, _} / {:error, _} et dépendent les unes des autres, with évite la « pyramide de case ». Il continue tant que chaque motif correspond, et court-circuite dès qu’un motif échoue.
defmodule Inscription do
def valider(params) do
with {:ok, email} <- extraire(params, :email),
{:ok, age} <- extraire(params, :age),
:ok <- verifier_majeur(age) do
{:ok, "Bienvenue #{email}"}
end
end
defp extraire(map, cle) do
case Map.fetch(map, cle) do
{:ok, v} -> {:ok, v}
:error -> {:error, "champ manquant : #{cle}"}
end
end
defp verifier_majeur(age) when age >= 18, do: :ok
defp verifier_majeur(_), do: {:error, "mineur"}
end
{
Inscription.valider(%{email: "a@b.com", age: 20}),
Inscription.valider(%{email: "a@b.com", age: 15}),
Inscription.valider(%{email: "a@b.com"})
}
Le premier motif qui ne correspond pas est renvoyé tel quel : on récupère directement l’{:error, raison} fautif, sans imbrication.
« Boucler » avec la récursion
Sans variable mutable, on répète en s’appelant soi-même. La clé : une clause d’arrêt (cas de base) + une clause qui se rapproche de cet arrêt.
defmodule MaListe do
# Cas de base : la liste vide a une longueur de 0
def longueur([]), do: 0
# Cas récursif : 1 + longueur du reste
def longueur([_tete | reste]), do: 1 + longueur(reste)
end
MaListe.longueur([:a, :b, :c, :d])
Accumulateur & récursion terminale
Pour de grandes données, on passe un accumulateur afin que l’appel récursif soit la toute dernière opération (récursion terminale) : la VM réutilise alors la même pile, sans risque de débordement.
defmodule Somme do
def total(liste), do: total(liste, 0)
defp total([], acc), do: acc
defp total([tete | reste], acc), do: total(reste, acc + tete)
end
Somme.total(Enum.to_list(1..1_000_000))
En pratique, on écrit rarement ces récursions à la main :
Enum/Stream(notebook suivant) couvrent 95 % des cas. Mais comprendre le mécanisme est essentiel pour lire du code Elixir.
case, cond, if : lequel choisir ?
note = 14
cond do
note >= 16 -> "Très bien"
note >= 14 -> "Bien"
note >= 10 -> "Passable"
true -> "Insuffisant" # `true` = branche par défaut
end
-
case: pattern matching sur une valeur (le plus idiomatique). -
cond: plusieurs conditions booléennes sans rapport entre elles. -
if/unless: un simple oui/non. Rappel : en Elixir, seulsfalseetnilsont « faux ».
À retenir
-
|>rend les transformations lisibles à condition que la donnée soit le 1er argument. -
withenchaîne des étapes faillibles et court-circuite proprement sur la 1re erreur. - On « boucle » par récursion (cas de base + cas récursif) ; l’accumulateur donne la récursion terminale.
-
case(motifs) /cond(conditions) /if(binaire) selon le besoin.
➡️ Suite : Enum, Stream et compréhensions