Skip to content

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 :

  1. Décomposer un problème en cas plus simples
  2. Identifier les cas de base (quand arrêter)
  3. Faire confiance à la récursion (le lutin qui gère le reste)
  4. Raisonner par induction (mathématiques !)
  5. 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 !)

// ✅ En Gleam : parenthèses comme en Python
fonction(arg1, arg2)

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

pub fn nomFonction(param1: Type1, param2: Type2) -> TypeRetour {
  ...
}

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

case expression {
  motif1 -> résultat1
  motif2 -> résultat2
  _ -> résultatParDéfaut
}

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 :

Cons(42, Cons(5, Vide))
      ↑       ↑
    tete    queue

tete = 42
queue = Cons(5, Vide)

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 :

somme [42, 5, 4]
= 42 + somme [5, 4]
     = 5 + somme [4]
          = 4 + somme []
               = 0

= 42 + 5 + 4 + 0
= 51


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

case condition {
  True -> résultatSiVrai
  False -> résultatSiFaux
}

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 ==

let x = 5   // Définition (créer une variable locale)
x == 5      // Comparaison (tester l'égalité)

❌ Mauvais pattern matching

// ERREUR : les cas ne couvrent pas tout
case lst {
  Vide -> 0
  // Manque le cas Cons !
}

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 e peut ê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 a en 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 :

  1. Identifier les cas de base
  2. Que faire si lst est Vide ?
  3. Y a-t-il d'autres cas simples ? (liste à 1 élément, n <= 0, etc.)

  4. Déconstruire avec pattern matching

    case lst {
      Vide -> ...
      Cons(tete, queue) -> ...
    }
    

  5. Penser comme un lutin

  6. Que dois-je faire avec tete ?
  7. Que dois-je faire avec queue ?
  8. Comment combiner les deux ?

  9. Vérifier la logique

  10. Est-ce que mon cas de base est correct ?
  11. Est-ce que je me rapproche du cas de base à chaque récursion ?

13. Tests en Gleam

Utiliser les exemples fournis

// Exemple dans le fichier
// taille(lst_ex1) -> 3

// Pour tester, lancez :
// $ gleam run

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

somme(Cons(42, Cons(5, Vide)))
= 42 + somme(Cons(5, Vide))
     = 5 + somme(Vide)
          = 0
= 42 + 5 + 0
= 47

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

  1. Ne pense JAMAIS à toute la liste - juste au premier élément
  2. Fais confiance à la récursion - elle gère le reste
  3. Teste sur des petits exemples - Vide, Cons(1, Vide), Cons(1, Cons(2, Vide))
  4. Lis les exemples fournis - ils t'aident à comprendre
  5. Commence par les fonctions simples - taille, somme, contient
  6. 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