1. Principes de base
Note d'intention
Gleam N'Est Qu'un Prétexte
Voici la vérité : Ce cours ne vous enseigne pas vraiment Gleam. Il vous enseigne comment penser les problèmes.
Ce Que Vous Apprenez Réellement
Quand vous écrivez :
pub type Liste(a) {
Vide
Cons(a, Liste(a))
}
pub fn taille(lst: Liste(a)) -> Int {
case lst {
Vide -> 0
Cons(_, queue) -> 1 + taille(queue)
}
}
Vous n'apprenez pas juste à calculer la taille d'une liste. Vous apprenez :
- Décomposer un problème en cas plus simples
- Identifier les cas de base (quand arrêter)
- Faire confiance à la récursion (le lutin qui gère le reste)
- Raisonner par induction (mathématiques !)
- Penser en termes de transformations plutôt que de mutations
Ces compétences sont universelles.
Surprise! Vous savez déjà programmer en Erlang! (et pas que)
-type liste(A) :: vide | {cons, A, liste(A)}.
taille(vide) -> 0;
taille({cons, _, Queue}) -> 1 + taille(Queue).
type 'a liste = Vide | Cons of 'a * 'a liste
let rec taille (lst : 'a liste) : int =
match lst with
| Vide -> 0
| Cons (_, queue) -> 1 + taille queue
data Liste a = Vide | Cons a (Liste a)
taille :: Liste a -> Int
taille Vide = 0
taille (Cons _ queue) = 1 + taille queue
type Liste[T] = tuple[()] | tuple[T, Liste[T]]
def taille(lst):
match lst:
case ():
return 0
case (_, queue):
return 1 + taille(queue)
0. Syntaxe de base
Les accolades { } délimitent les blocs. La dernière expression d'une fonction est automatiquement sa valeur de retour.
Appeler des Fonctions en Gleam
Syntaxe de base (AVEC parenthèses, comme en Python !)
Exemples concrets
// Python: max(5, 3)
// Gleam:
int.max(5, 3)
// Python: taille(liste)
// Gleam:
taille(lst)
// Python: range(0, 10)
// Gleam:
range2(0, 10)
Appels imbriqués
// L'argument est une expression complexe
taille(Cons(1, Vide))
// Appels imbriqués : le résultat intérieur devient l'argument extérieur
somme(take(3, lst))
// Plusieurs niveaux
Cons(tete, take(n - 1, queue))
Règle Simple
- Parenthèses = appeler une fonction :
fonction(arg) - Parenthèses imbriquées = calculer d'abord, puis passer le résultat
Créer des Fonctions en Gleam
Structure Complète
// La signature de type est intégrée dans la définition
pub fn doublerValeur(n: Int) -> Int {
// ↑ ↑ ↑ ↑ ↑
// pub nom param type retour
n * 2
// ↑
// corps (dernière expression = valeur retournée)
}
Pas de return ! La dernière expression est automatiquement renvoyée.
La Signature de Type
Format
Lecture : La fonction prend param1 de type Type1, param2 de type Type2, et retourne un TypeRetour.
Exemples Simples
// Constante
pub const reponse: Int = 42
// Fonction à 1 paramètre
pub fn doublerValeur(n: Int) -> Int {
n * 2
}
// Fonction à 2 paramètres
pub fn additionner(x: Int, y: Int) -> Int {
x + y
}
// Fonction à 3 paramètres
pub fn calculer(a: Int, b: Int, c: Int) -> Int {
a + b * c
}
// Fonction générique (a = n'importe quel type)
pub fn taille(lst: Liste(a)) -> Int {
...
}
1. Types de Données Personnalisés
Définir un type personnalisé
pub type Liste(a) { // 'a' est un type générique (Int, String, Bool, etc.)
Vide // Premier cas : liste vide
Cons(a, Liste(a)) // Deuxième cas : tête + queue
}
Lecture : Une Liste de a est SOIT Vide SOIT Construite avec un élément de type a et une autre Liste.
Exemples
// Liste d'entiers : [42, 5, 4]
Cons(42, Cons(5, Cons(4, Vide)))
// Liste de booléens : [True, False]
Cons(True, Cons(False, Vide))
// Liste vide
Vide
2. Pattern Matching avec case
Syntaxe de base
Exemple simple
pub fn taille(lst: Liste(a)) -> Int {
case lst {
Vide -> 0 // Cas 1 : liste vide
Cons(tete, queue) -> 1 + taille(queue) // Cas 2 : déconstruit la liste
}
}
Décomposition :
Pattern matching sur un tuple
case #(lst1, lst2) {
#(Vide, Vide) -> ... // Les deux sont vides
#(Vide, _) -> ... // Première vide, deuxième peu importe
#(_, Vide) -> ... // Deuxième vide, première peu importe
#(Cons(t1, q1), Cons(t2, q2)) -> ... // Les deux non-vides
}
Le symbole _ = "je m'en fiche de cette valeur"
Note : #(a, b) est la syntaxe des tuples en Gleam.
3. Récursivité : Penser comme un Lutin
La Méthode du Lutin
Ne JAMAIS penser à TOUTE la liste ! Pense seulement au premier élément.
Étape 1 : Identifier le(s) CAS DE BASE
Le cas de base, c'est quand on peut répondre immédiatement sans récursion.
pub fn taille(lst: Liste(a)) -> Int {
case lst {
Vide -> 0 // ✅ CAS DE BASE : liste vide = 0
Cons(_, queue) -> ...
}
}
Étape 2 : Le Cas Récursif
"Je suis un lutin, je ne traite QUE le premier élément, puis je délègue le reste à un autre lutin."
pub fn somme(lst: Liste(Int)) -> Int {
case lst {
Vide -> 0 // CAS DE BASE
Cons(tete, queue) ->
tete + somme(queue) // Je traite tete, je délègue queue
// ↑ ↑
// moi autre lutin
}
}
Visualisation :
4. Conditions avec case sur un Bool
⚠️ Gleam n'a pas de if-then-else ! On fait du pattern matching sur un booléen.
Syntaxe
Exemples
// Test d'égalité
case tete == e {
True -> ...
False -> ...
}
// Test numérique
case n > 0 {
True -> Cons(tete, take(n - 1, queue))
False -> Vide
}
// Modulo (reste de division)
case tete % 2 == 0 {
True -> "pair"
False -> "impair"
}
5. Opérateurs Booléens
// ET logique
True && True // -> True
True && False // -> False
// OU logique
True || False // -> True
False || False // -> False
// Égalité
42 == 42 // -> True
42 == 5 // -> False
// Différence
42 != 5 // -> True
// Comparaisons
5 > 3 // -> True
5 >= 5 // -> True
5 < 10 // -> True
6. Stratégies de Résolution
Stratégie 1 : Fonction de Lecture (compte, contient, etc.)
Question : Que faire de la tête ? Que faire de la queue ?
pub fn compte(e: a, lst: Liste(a)) -> Int {
case lst {
Vide -> 0 // CAS DE BASE
Cons(tete, queue) ->
case tete == e {
True -> 1 + compte(e, queue) // J'ai trouvé e, j'ajoute 1 au reste
False -> compte(e, queue) // Pas trouvé, je demande juste le reste
}
}
}
Stratégie 2 : Prédicat (renvoie Bool)
Question : Quelle condition casse la promesse du prédicat ?
✅ Version préférable : récursion terminale (TCO)
L'appel récursif tousVrais(queue) est la dernière opération effectuée : c'est une récursion terminale, optimisable par le compilateur.
pub fn tousVrais(lst: Liste(Bool)) -> Bool {
case lst {
Vide -> True
Cons(tete, queue) ->
case tete {
False -> False // Court-circuit immédiat
True -> tousVrais(queue) // Appel terminal ✅
}
}
}
Version avec && : correcte, mais pas TCO
tete && tousVrais(queue) n'est pas en position terminale : l'opérateur && doit encore être appliqué après le retour de l'appel récursif. Le compilateur ne peut pas l'optimiser. C'est paradoxal car cette manière d'écrire montre qu'on a vraiment compris comment fonctionnent les opérations booléennes.
pub fn tousVrais(lst: Liste(Bool)) -> Bool {
case lst {
Vide -> True
Cons(tete, queue) -> tete && tousVrais(queue) // ⚠️ pas TCO
}
}
Note : Les opérateurs booléens sont dits "lazy". Lors de l'évaluation de x && y, si x est False, alors y ne sera pas évalué. Pareil pour x || y : si x est vrai, y ne sera pas évalué.
Stratégie 3 : Fonction de Création (pairs, dupliquer, etc.)
Question : Dois-je inclure la tête ? Comment construire avec Cons ?
pub fn pairs(lst: Liste(Int)) -> Liste(Int) {
case lst {
Vide -> Vide // CAS DE BASE
Cons(tete, queue) ->
case tete % 2 == 0 {
True -> Cons(tete, pairs(queue)) // Tête paire : je la garde et je demande le reste
False -> pairs(queue) // Tête impaire : je la jette, je demande juste le reste
}
}
}
7. Pattern Matching Avancé
Cas avec plusieurs niveaux
pub fn supprimerFin(lst: Liste(a)) -> Liste(a) {
case lst {
Vide -> Vide // Liste vide
Cons(_, Vide) -> Vide // Un seul élément, on renvoie Vide
Cons(t, q) -> Cons(t, supprimerFin(q)) // Plusieurs éléments
}
}
Tuple de listes
pub fn egales(lst1: Liste(a), lst2: Liste(a)) -> Bool {
case #(lst1, lst2) {
#(Vide, Vide) -> True // Les deux sont vides
#(Vide, _) -> False // Première vide, deuxième non vide
#(_, Vide) -> False // Deuxième vide, première non vide
#(Cons(t1, q1), Cons(t2, q2)) -> // Cas récursif
t1 == t2 && egales(q1, q2)
}
}
9. Erreurs Courantes
❌ Oublier le cas de base
Dans ce cas, la fonction ne fait que s'appeler indéfiniment elle-même. Le principe de la récursivité est que les appels doivent toujours converger vers un cas de base pour terminer.
// ERREUR : boucle infinie !
pub fn somme(lst: Liste(Int)) -> Int {
case lst {
Cons(t, q) -> t + somme(q) // Et si lst est Vide ???
}
}
❌ Utiliser if (qui n'existe pas en Gleam !)
// ERREUR : Gleam n'a pas de if-then-else !
if n > 0 {
n
}
// ✅ À la place, utiliser case :
case n > 0 {
True -> n
False -> 0
}
❌ Confondre = et ==
❌ Mauvais pattern matching
10. Vocabulaire Gleam
| Terme | Signification |
|---|---|
type |
Définir un nouveau type |
pub |
Rendre public (visible depuis d'autres modules) |
fn |
Définir une fonction |
case ... { } |
Pattern matching |
let ... = |
Variable locale |
-> |
"devient" ou "retourne" |
_ |
"je m'en fous" (wildcard) |
#(a, b) |
Tuple |
Int, Float, String, Bool |
Types de base |
11. Comment Aborder les Exercices
Démarche pour contient
Objectif : Vérifier si un élément e est dans la liste.
Questions à se poser :
- Cas de base : Si la liste est vide, est-ce que
epeut être dedans ? - Cas récursif : Si la tête est égale à
e, qu'est-ce que je retourne ? - Si la tête n'est pas
e, que dois-je faire avec la queue ?
Astuce : Utilise l'opérateur OU || pour combiner les conditions.
Démarche pour ajouterFin
Objectif : Ajouter un élément à la fin de la liste.
Questions à se poser :
- Cas de base : Si la liste est vide, comment créer une liste avec juste
e? - Cas récursif : Je garde la tête, mais que dois-je faire avec la queue ?
Trace mentale :
ajouterFin(9, [1, 2, 3])
= Je garde 1, et j'ajoute 9 à la fin de [2, 3]
= Je garde 2, et j'ajoute 9 à la fin de [3]
= Je garde 3, et j'ajoute 9 à la fin de []
= Cas de base : créer [9]
Démarche pour range2
Objectif : Créer la liste [a, a+1, a+2, ..., b-1]
Questions à se poser :
- Cas de base : Quand est-ce que je dois arrêter ? (quand a >= b)
- Cas récursif : Je mets
aen tête, puis quoi en queue ?
Réflexion : C'est similaire à une boucle for i in range(a, b) en Python.
12. Méthode de Travail
Pour chaque fonction :
- Identifier les cas de base
- Que faire si
lstestVide? -
Y a-t-il d'autres cas simples ? (liste à 1 élément, n <= 0, etc.)
-
Déconstruire avec pattern matching
-
Penser comme un lutin
- Que dois-je faire avec
tete? - Que dois-je faire avec
queue? -
Comment combiner les deux ?
-
Vérifier la logique
- Est-ce que mon cas de base est correct ?
- Est-ce que je me rapproche du cas de base à chaque récursion ?
13. Tests en Gleam
Utiliser les exemples fournis
Vous pouvez aussi tester dans le playground en ligne : play.gleam.run
todo
// Pendant le développement
pub fn maFonction(x: Int) -> Int {
todo as "À implémenter"
}
// Remplacer progressivement les todo par du vrai code
14. Astuces de Débogage
Afficher des valeurs
import gleam/io
pub fn maFonction(x: Int) -> Int {
let _ = io.debug(x) // Affiche la valeur dans la console
...
}
Simplifier le problème
// Au lieu de résoudre tout de suite [1,2,3,4,5]
// Teste d'abord avec :
// Vide
// Cons(1, Vide)
// Cons(1, Cons(2, Vide))
Écrire la trace
15. Complexité (Pour Information)
| Opération | Complexité |
|---|---|
Cons(x, lst) |
O(1) - instantané |
ajouterFin(x, lst) |
O(n) - doit tout parcourir |
taille(lst) |
O(n) - compte tous les éléments |
concat(lst1, lst2) |
O(n) où n = taille de lst1 |
Règle : Préfère construire avec Cons plutôt qu'avec ajouterFin.
Checklist Avant de Coder
- [ ] J'ai identifié le(s) cas de base
- [ ] Je sais quoi faire avec
Vide - [ ] Je sais quoi faire avec
tete - [ ] Je sais quoi faire avec
queue - [ ] Je comprends comment combiner tête et résultat récursif
- [ ] J'ai vérifié que je me rapproche du cas de base
Conseils Finaux
- Ne pense JAMAIS à toute la liste - juste au premier élément
- Fais confiance à la récursion - elle gère le reste
- Teste sur des petits exemples - Vide, Cons(1, Vide), Cons(1, Cons(2, Vide))
- Lis les exemples fournis - ils t'aident à comprendre
- Commence par les fonctions simples - taille, somme, contient
- Utilise les fonctions déjà écrites - range pour range2, etc.
Tu es Prêt !
Avec ces concepts, tu peux résoudre TOUS les exercices du fichier. N'oublie pas :
- Pattern matching pour déconstruire
- Récursion pour traiter
- Cas de base pour arrêter