Skip to content

2. Le Type Option

Le Milliard de Dollars

En 1965, Tony Hoare invente null pour le langage ALGOL. En 2009, il présente ses excuses devant une conférence de 2000 développeurs :

"I call it my billion-dollar mistake. It has caused a billion dollars of pain and damage in the last forty years."

null est dangereux parce qu'il est invisible : n'importe quelle variable peut en être un, et le compilateur ne vérifie rien. Le crash n'arrive qu'à l'exécution, parfois en production. Le déréférencement de pointeur null (CWE-476) figure chaque année parmi les vulnérabilités les plus fréquentes du classement MITRE CWE Top 25, aux côtés des débordements de tampon et des injections.

La réponse moderne - adoptée par Rust, Swift, Kotlin, Haskell, Gleam, et progressivement par Java et TypeScript - est de rendre l'absence explicite dans le type. Si une fonction peut ne rien retourner, son type de retour le dit. Le compilateur refuse alors tout code qui ignorerait ce cas.

C'est exactement ce que fait Option.


Le Problème

Que Retourner Quand Ça Échoue ?

// Comment écrire cette fonction ?
pub fn dernierElement(lst: Liste(a)) -> ???

// Problème : Que retourner si la liste est vide ?
// Pas d'élément à retourner !

En d'autres langages :

  • Python/Java : None ou null → crashes possibles
  • C : Valeur spéciale (-1, 0) → ambiguë
  • Exceptions : Lourd et imprévisible

En Gleam : Le type Option !


Définition de Option

Le Type

pub type Option(a) {
  None        // Absence de valeur
  Some(a)     // Présence d'une valeur de type a
}

Lecture : Un Option(a) est SOIT None (rien), SOIT Some contenant une valeur de type a.

Exemples de Valeurs

// Option(Int)
None          // Pas d'entier
Some(42)      // L'entier 42

// Option(String)
None          // Pas de chaîne
Some("hello") // La chaîne "hello"

// Option(Bool)
None          // Pas de booléen
Some(True)    // Le booléen True

Intuition

Option = Boîte Optionnelle

Some(5)  // Boîte contenant 5
None     // Boîte vide

Analogie :

  • Boîte fermée avec un cadeauSome(valeur)
  • Boîte videNone

En Comparaison avec d'Autres Langages

Langage Équivalent Problème
Python None Pas typé, crash possible
Java null NullPointerException
JavaScript null/undefined Confusion
Rust Option<T> Même concept ! ✅
Haskell Maybe a Même concept ! ✅
Elm Maybe a Même concept ! ✅
Gleam Option(a) Sûr à la compilation ✅

Utilisation avec les Listes

Exemple 1 : premier

// Renvoie le premier élément d'une liste
pub fn premier(lst: Liste(a)) -> Option(a) {
  case lst {
    Vide -> None                 // Pas d'élément
    Cons(tete, _) -> Some(tete) // On a un élément !
  }
}

Utilisation :

premier(Cons(1, Cons(2, Cons(3, Vide))))
// -> Some(1)

premier(Vide)
// -> None

Exemple 2 : dernierElement

// Renvoie le dernier élément d'une liste
pub fn dernierElement(lst: Liste(a)) -> Option(a) {
  case lst {
    Vide -> None                                  // Liste vide
    Cons(tete, Vide) -> Some(tete)                // Un seul élément
    Cons(_, queue) -> dernierElement(queue)        // Continue la recherche
  }
}

Utilisation :

dernierElement(Cons(1, Cons(2, Cons(3, Vide))))
// -> Some(3)

dernierElement(Vide)
// -> None

dernierElement(Cons(42, Vide))
// -> Some(42)

Trace d'exécution :

dernierElement [1, 2, 3]
= dernierElement [2, 3]          (pas le dernier)
= dernierElement [3]             (pas le dernier)
= Some(3)                        (un seul élément !)


Pattern Matching sur Option

Déconstruire un Option

case monOption {
  None -> ...          // Cas où il n'y a rien
  Some(valeur) -> ...  // Cas où on a une valeur
}

Exemple : Afficher un Résultat

import gleam/int

pub fn afficherResultat(resultat: Option(Int)) -> String {
  case resultat {
    None -> "Aucun résultat"
    Some(n) -> "Le résultat est : " <> int.to_string(n)
  }
}

Utilisation :

afficherResultat(Some(42))
// -> "Le résultat est : 42"

afficherResultat(None)
// -> "Aucun résultat"


Fonctions Utiles sur Option

option.unwrap : Valeur par Défaut

import gleam/option

// Si None, utilise la valeur par défaut
option.unwrap(Some(5), or: 0)
// -> 5

option.unwrap(None, or: 0)
// -> 0

option.unwrap(Some("Alice"), or: "inconnu")
// -> "Alice"

option.unwrap(None, or: "inconnu")
// -> "inconnu"

Utilisation pratique :

// Obtenir le premier élément ou 0 par défaut
pub fn premierOuZero(lst: Liste(Int)) -> Int {
  premier(lst)
  |> option.unwrap(or: 0)
}

premierOuZero(Cons(1, Cons(2, Cons(3, Vide))))
// -> 1

premierOuZero(Vide)
// -> 0

option.map : Transformer la Valeur

// Applique une fonction si Some, sinon reste None
option.map(Some(5), fn(x) { x * 2 })
// -> Some(10)

option.map(None, fn(x) { x * 2 })
// -> None

option.map(Some("hello"), string.length)
// -> Some(5)

Cas d'usage :

// Doubler le premier élément
pub fn doublerPremier(lst: Liste(Int)) -> Option(Int) {
  premier(lst)
  |> option.map(fn(x) { x * 2 })
}

doublerPremier(Cons(5, Cons(10, Cons(15, Vide))))
// -> Some(10)

doublerPremier(Vide)
// -> None


Erreurs Courantes

❌ Oublier le Some

// ERREUR
pub fn premier(lst: Liste(a)) -> Option(a) {
  case lst {
    Vide -> None
    Cons(tete, _) -> tete  // Manque Some !
  }
}

// ✅ CORRECT
pub fn premier(lst: Liste(a)) -> Option(a) {
  case lst {
    Vide -> None
    Cons(tete, _) -> Some(tete)
  }
}

❌ Oublier le Cas None

// ERREUR : Pattern matching incomplet
pub fn utiliser(m: Option(Int)) -> Int {
  case m {
    Some(x) -> x * 2
    // Manque le cas None ! Le compilateur refuse.
  }
}

// ✅ CORRECT
pub fn utiliser(m: Option(Int)) -> Int {
  case m {
    Some(x) -> x * 2
    None -> 0
  }
}

Chaîner des Option

Problème : Option Imbriqués

// Obtenir le premier élément du dernier élément (liste de listes)

// Version naïve → type bizarre
pub fn getPremierDuDernier(listes: Liste(Liste(a))) -> Option(Option(a)) {
  case dernierElement(listes) {
    None -> None
    Some(derniereListe) -> Some(premier(derniereListe))
  }
}
// Résultat : Option(Option(a)) - pas idéal

Solution : option.then

// option.then "aplatit" les Option imbriqués
pub fn getPremierDuDernier(listes: Liste(Liste(a))) -> Option(a) {
  dernierElement(listes)
  |> option.then(premier)
}

Exemples :

// [[1,2], [3,4], [5,6]] → premier de [5,6]
// -> Some(5)

// [[1,2], [3,4], []] → premier de [] = None
// -> None

// [] → pas de dernier
// -> None


Exemple Complet : minimum

Trouver le Minimum d'une Liste d'Entiers

pub fn minimum(lst: Liste(Int)) -> Option(Int) {
  case lst {
    Vide -> None
    Cons(tete, queue) ->
      case minimum(queue) {
        None -> Some(tete)              // tete est le seul élément
        Some(min_queue) ->
          case tete < min_queue {
            True -> Some(tete)          // tete est plus petit
            False -> Some(min_queue)    // min_queue est plus petit
          }
      }
  }
}

Utilisation :

minimum(Cons(3, Cons(1, Cons(4, Cons(1, Cons(5, Vide))))))
// -> Some(1)

minimum(Cons(42, Vide))
// -> Some(42)

minimum(Vide)
// -> None

Trace d'exécution :

minimum [3, 1, 4]
= case minimum [1, 4] of
    Some(1) -> case 3 < 1 of
                 False -> Some(1)
= Some(1)

minimum [5]
= case minimum [] of
    None -> Some(5)
= Some(5)


Exercices

Exercice 1 : elementA

Écris une fonction qui retourne l'élément à la position n (commence à 0).

pub fn elementA(n: Int, lst: Liste(a)) -> Option(a)

// Exemples attendus
elementA(0, [1, 2, 3])  // -> Some(1)
elementA(2, [1, 2, 3])  // -> Some(3)
elementA(5, [1, 2, 3])  // -> None
elementA(-1, [1, 2, 3]) // -> None

Exercice 2 : maximum

Écris une fonction qui retourne le plus grand élément.

pub fn maximum(lst: Liste(Int)) -> Option(Int)

// Exemples attendus
maximum([3, 1, 4, 1, 5])  // -> Some(5)
maximum([])               // -> None

Indice : Similaire à minimum, mais avec > au lieu de <.

Exercice 3 : trouver

Écris une fonction qui retourne le premier élément satisfaisant un prédicat.

pub fn trouver(pred: fn(a) -> Bool, lst: Liste(a)) -> Option(a)

// Exemples attendus
trouver(fn(x) { x > 3 }, [1, 2, 3, 4, 5])  // -> Some(4)
trouver(fn(x) { x > 10 }, [1, 2, 3])       // -> None

Pourquoi Option est Important ?

1. Sécurité à la Compilation

// Le compilateur refuse ce code :
let x = premier(Vide)
let y = x + 1  // ERREUR : x est Option(Int), pas Int !

// ✅ Gleam force à gérer le cas None
let x = premier(Vide)
let y = case x {
  Some(n) -> n + 1
  None -> 0
}

Pas de crash à l'exécution !

2. Code Explicite

// La signature dit tout
pub fn dernierElement(lst: Liste(a)) -> Option(a)
//                                      ↑
//                                      "Peut ne rien retourner !"

// vs en Python
def last_element(lst):  # Peut retourner None... ou crasher ?
    ...

3. Forcer à Penser aux Cas d'Erreur

// Tu DOIS gérer None - le compilateur le vérifie
case premier(maListe) {
  None -> ...    // Obligé de penser à ce cas
  Some(x) -> ...
}

Option vs Autres Approches

Approche Problème
Valeur spéciale (-1, "") Ambiguë - -1 peut être un vrai résultat
Exception Invisible dans la signature, peut crasher
null/None Python Pas vérifié à la compilation
Option Explicite, vérifié, sans surprise ✅

Le Pattern Récurrent

pub fn maFonction(entree: ...) -> Option(Resultat) {
  case entree {
    CasVide -> None              // Échec
    CasNonVide(données) ->
      Some(résultat)             // Succès
  }
}

Exemples de situations :

  • Liste vide → None
  • Division par zéro → None
  • Clé inexistante dans dictionnaire → None
  • Parsing échoué → None

Résumé

Qu'est-ce que Option ?

pub type Option(a) {
  None
  Some(a)
}
  • None : Absence de valeur (échec, vide, erreur)
  • Some(valeur) : Présence d'une valeur

Quand Utiliser Option ?

Utilise Option(a) comme type de retour quand :

  • ✅ La fonction peut ne rien retourner
  • ✅ L'entrée peut être vide
  • ✅ L'opération peut échouer

Fonctions Clés du Module gleam/option

// Valeur par défaut
option.unwrap(opt, or: valeur_par_defaut)

// Transformer la valeur si Some
option.map(opt, fn(x) { ... })

// Chaîner des opérations qui retournent Option
option.then(opt, ma_fonction)

Le Pattern

case monOption {
  None -> ...         // Gérer l'absence
  Some(valeur) -> ... // Utiliser la valeur
}

Conseil Final

Si ta fonction peut "échouer" ou "ne rien retourner", utilise Option !

// ✅ Bon
pub fn premier(lst: Liste(a)) -> Option(a)

// ❌ Mauvais : impossible de représenter l'échec
pub fn premier(lst: Liste(a)) -> a  // Et si la liste est vide ???

Option = Sécurité + Clarté + Pas de surprise !


Pour Aller Plus Loin

Une fois que tu maîtrises Option, tu peux découvrir Result : pour les erreurs avec message (Ok(valeur) / Error(raison)).

Mais pour l'instant, Option est déjà très puissant !