How-to Pattern Matching
Sommaire
- 1. Match basique - le switch qui renvoie une valeur
- 2. Capture de variable
- 3. Guards - filtrer avec une condition
- 4. OR patterns - regrouper les cas
- 5. Tuples - destructuration positionnelle
- 6. Star patterns - capturer le reste
- 7. Struct patterns - dispatch par type
- Struct + guard
- 8. Patterns imbriqués
- 9. Match comme expression
- 10. Pièges courants
- Ordre des bras
- Guard trop large
- Match non exhaustif
- Récapitulatif
Objectif : maîtriser le pattern matching de zéro à avancé. Chaque section introduit un concept, l'illustre, et montre quand l'utiliser.
1. Match basique - le switch qui renvoie une valeur
match est une expression : il produit un résultat.
status = 404
message = match status {
200 => { "OK" }
404 => { "Not Found" }
500 => { "Server Error" }
_ => { "Unknown" }
}
print(message)
# → "Not Found"
Le _ (wildcard) attrape tout ce qui ne matche pas. Sans lui, une valeur non couverte déclenche une erreur.
2. Capture de variable
Si un pattern est un identifiant (pas un littéral), il capture la valeur :
x = 42
match x {
0 => { "zero" }
n => { f"got {n}" }
}
# → "got 42"
n est lié dans le scope du bras. La capture n'affecte pas le scope extérieur.
3. Guards - filtrer avec une condition
Un guard (if condition) affine un pattern. Le pattern matche d'abord, puis le guard est évalué :
classify = (score) => {
match score {
n if n >= 90 => { "excellent" }
n if n >= 70 => { "good" }
n if n >= 50 => { "pass" }
_ => { "fail" }
}
}
classify(85)
# → "good"
classify(30)
# → "fail"
Le guard a accès aux variables capturées par le pattern. Si le guard échoue, le match passe au bras suivant.
4. OR patterns - regrouper les cas
Le | combine plusieurs patterns en un seul bras :
day = 6
match day {
1 | 2 | 3 | 4 | 5 => { "weekday" }
6 | 7 => { "weekend" }
_ => { "invalid" }
}
# → "weekend"
Un seul bras au lieu de cinq. Le premier sous-pattern qui matche gagne.
5. Tuples - destructuration positionnelle
point = tuple(0, 5)
match point {
(0, 0) => { "origin" }
(0, y) => { f"y-axis at {y}" }
(x, 0) => { f"x-axis at {x}" }
(x, y) => { f"({x}, {y})" }
}
# → "y-axis at 5"
Les positions comptent. Un littéral vérifie la valeur, un identifiant la capture.
6. Star patterns - capturer le reste
L'opérateur * dans un pattern collecte les éléments restants :
values = list(1, 2, 3, 4, 5)
# Tête et reste
match values {
(first, *rest) => { print("first:", first, "rest:", rest) }
}
# → first: 1 rest: [2, 3, 4, 5]
# Dernier élément
match values {
(*init, last) => { print("last:", last) }
}
# → last: 5
# Premier, milieu, dernier
match values {
(a, *mid, z) => { print(a, mid, z) }
}
# → 1 [2, 3, 4] 5
7. Struct patterns - dispatch par type
Le pattern matching sur structures vérifie le type puis lie les champs :
struct Circle { radius }
struct Rect { width; height; }
area = (shape) => {
match shape {
Circle{radius} => { 3.14159 * radius ** 2 }
Rect{width, height} => { width * height }
_ => { 0 }
}
}
area(Circle(5))
# → 78.53975
area(Rect(3, 4))
# → 12
C'est du dispatch par type sans hiérarchie de classes. Chaque bras teste le type et destructure les champs en une opération.
Struct + guard
struct Point { x; y; }
classify_point = (p) => {
match p {
Point{x, y} if x == 0 and y == 0 => { "origin" }
Point{x, y} if y == 0 => { "x-axis" }
Point{x, y} if x == 0 => { "y-axis" }
Point{x, y} => { "general" }
}
}
classify_point(Point(0, 0))
# → "origin"
classify_point(Point(3, 0))
# → "x-axis"
8. Patterns imbriqués
Les patterns se composent en profondeur :
data = list(1, tuple(2, 3))
match data {
(a, (b, c)) => { a + b + c }
}
# → 6
# Profondeur arbitraire
deep = list(1, list(2, list(3, 4)))
match deep {
(a, (b, (c, d))) => { a + b + c + d }
}
# → 10
9. Match comme expression
match renvoie la valeur du bras exécuté. On peut l'assigner ou le passer directement :
grade = (score) => {
match score {
n if n >= 90 => { "A" }
n if n >= 80 => { "B" }
n if n >= 70 => { "C" }
_ => { "F" }
}
}
print(grade(92))
# → "A"
10. Pièges courants
Ordre des bras
Le premier pattern qui matche gagne. Un wildcard en premier masque tout le reste :
match 42 {
_ => { "catch-all" }
42 => { "exact" }
}
# → "catch-all" (le 42 n'est jamais testé)
Guard trop large
Un guard permissif masque les bras suivants :
match 10 {
n if n > 0 => { "positive" }
10 => { "ten" }
}
# → "positive" ("ten" est inatteignable)
Match non exhaustif
Sans wildcard ni capture finale, une valeur non couverte est une erreur :
match 99 {
1 => { "one" }
2 => { "two" }
}
# → Error: No matching pattern
Le compilateur ne vérifie pas l'exhaustivité statiquement. Le
_est ton filet de sécurité.
Récapitulatif
| Pattern | Syntaxe | Rôle |
|---|---|---|
| Littéral | 42, "hello", True |
Vérifie une valeur exacte |
| Variable | n, x |
Capture la valeur |
| Wildcard | _ |
Matche tout, ne capture rien |
| Guard | pattern if cond |
Filtre conditionnel |
| OR | a | b | c |
Regroupe plusieurs patterns |
| Tuple | (a, b, c) |
Destructure par position |
| Star | (first, *rest) |
Capture le reste |
| Struct | Point{x, y} |
Vérifie le type + destructure |
Le pattern matching est un outil de décision unique : une entrée, un chemin, un résultat. Pas de backtracking, pas d'ambiguïté. Le premier bras qui matche s'exécute, les autres n'existent pas.