Skip to content

1. Principes de base

Note d'intention

Elm N'Est Qu'un Prétexte

Voici la vérité : Ce cours ne vous enseigne pas vraiment Elm. Il vous enseigne comment penser les problèmes.

Ce Que Vous Apprenez Réellement

Quand vous écrivez :

taille : Liste a -> Int
taille lst =
    case lst of
        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 OCaml! (et pas que)

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

0. Syntaxe de base

Je ne m'étendrai pas sur l'indentation qui est nécessaire comme en python, contentez vous de faire comme je fais.

Appeler des Fonctions en Elm

Syntaxe de base (PAS de parenthèses !)

-- ❌ PAS comme en Python
fonction(arg1, arg2)

-- ✅ En Elm : juste des espaces
fonction arg1 arg2

Exemples concrets

-- Python: max(5, 3)
-- Elm:
max 5 3

-- Python: range(0, 10)
-- Elm:
range2 0 10

-- Python: taille(liste)
-- Elm:
taille lst

Rôle des Parenthèses

Les parenthèses servent à GROUPER, pas à appeler !

-- Si l'argument est une expression complexe
taille (Cons 1 Vide)
--     ↑            ↑
--     Sans ces parenthèses, Elm lirait: taille Cons 1 Vide (3 arguments!)

-- Appels imbriqués
somme (take 3 lst)
--    ↑          ↑
--    Groupe "take 3 lst" comme UN argument

-- Plusieurs niveaux
Cons tete (take (n - 1) queue)
--        ↑                  ↑
--        Sans ça: Cons tete take (n - 1) queue (Cons avec 4 arguments: erreur)
-- pareil pour le (n - 1)

Règle Simple

  • Pas de parenthèses = appel direct : fonction arg
  • Avec parenthèses = grouper une expression : fonction (expression)

Mnémotechnique : Parenthèses = calculer d'abord, puis passer le résultat.


Créer des Fonctions en Elm

Structure Complète

-- 1. Signature de type (optionnelle mais recommandée)
doublerValeur : Int -> Int
--  ↑           ↑      ↑
--  nom      entrée  sortie

-- 2. Définition
doublerValeur n = n * 2
--  ↑         ↑   ↑
--  nom    param  corps

🔍 La Signature de Type

Format

nomFonction : Type1 -> Type2 -> ... -> TypeRetour

Lecture : La fonction prend des paramètres de Type1, Type2, etc., et retourne un TypeRetour.

Exemples Simples

-- Fonction sans paramètre (constante)
reponse : Int
reponse = 42

-- Fonction à 1 paramètre
doublerValeur : Int -> Int
doublerValeur n = n * 2

-- Fonction à 2 paramètres
additionner : Int -> Int -> Int
additionner x y = x + y

-- Fonction à 3 paramètres
calculer : Int -> Int -> Int -> Int
calculer a b c = a + b * c

1. Types de Données Personnalisés

Définir un type personnalisé

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 of
    motif1 -> résultat1
    motif2 -> résultat2
    _      -> résultatParDéfaut

Exemple simple

taille : Liste a -> Int
taille lst =
    case lst of
        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) of
    (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"


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.

taille : Liste a -> Int
taille lst =
    case lst of
        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."

somme : Liste Int -> Int
somme lst =
    case lst of
        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 if-then-else

⚠️ Attention, contrairement à python, les conditions représentent une valeur renvoyée

Syntaxe

if condition then
    résultatSiVrai
else
    résultatSiFaux

⚠️ OBLIGATOIRE : Toujours un else en Elm !

Exemples

-- Test d'égalité
if tete == e then
    ...
else
    ...

-- Test numérique
if n > 0 then
    Cons tete (take (n - 1) queue)
else
    Vide

-- Modulo (reste de division)
if modBy 2 x == 0 then
    "pair"
else
    "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 ?

compte : a -> Liste a -> Int
compte e lst =
    case lst of
        Vide -> 0  -- CAS DE BASE
        Cons tete queue ->
            if tete == e then
                1 + compte e queue  -- J'ai trouvé e, j'ajoute 1 au reste
            else
                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 ?

Cette manière de faire n'est pas bonne (bien que correcte), elle montre que vous n'avez pas encore bien assimilé le calcul booléen

-- "Tous vrais" : un seul False casse la promesse
tousVrais : Liste Bool -> Bool
tousVrais lst =
    case lst of
        Vide -> True  -- Promesse non violée (rien à vérifier)
        Cons tete queue ->
            if tete == False then
                False  -- Promesse cassée !
            else
                tousVrais queue  -- Continue de vérifier
Cette manière de faire montre que vous avez assimilé le calcul booléen.

-- Ici la promesse est violée quand dès que tete est False
tousVrais : Liste Bool -> Bool
tousVrais lst =
    case lst of
        Vide -> True
        Cons tete queue -> tete && tousVrais queue

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 ?

pairs : Liste Int -> Liste Int
pairs lst =
    case lst of
        Vide -> Vide  -- CAS DE BASE
        Cons tete queue ->
            if modBy 2 tete == 0 then
                Cons tete (pairs queue)  -- Tête paire : je la garde  et je demande le reste
            else
                pairs queue              -- Tête impaire : je la jette, je demande juste le reste

7. Pattern Matching Avancé

Cas avec plusieurs niveaux

supprimerFin : Liste a -> Liste a
supprimerFin lst =
    case lst of
        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

egales : Liste a -> Liste a -> Liste a
egales lst1 lst2 =
    case (lst1, lst2) of
        (Vide, Vide) -> True         -- Les deux sont vides
        (Vide, _) -> False           -- Première vide, deuxième non vide (sinon on serait dans le cas 1)
        (_, Vide) -> False           -- Deuxième vide : première non vide (idem)
        (Cons t1 q1, Cons t2 q2) ->  -- Cas récursif
                (t1==t2) && egales t1 t2  

9. Erreurs Courantes

❌ Oublier le cas de base

-- ERREUR : boucle infinie !
somme lst =
    case lst of
        Cons t q -> t + somme q  -- Et si lst est Vide ???

❌ Oublier le else

-- ERREUR : else obligatoire en Elm !
if n > 0 then
    n
-- Manque else !

❌ Confondre = et ==

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

❌ Mauvais pattern matching

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

10. Vocabulaire Elm

Terme Signification
type Définir un nouveau type
case ... of Pattern matching
let ... in Variables locales
-> "devient" ou "retourne"
_ "je m'en fous" (wildcard)
comparable Type qui peut être comparé (<, >, ==)
number Type numérique (Int ou Float)

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 of
        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 dans Elm

Utiliser les exemples fournis

-- Exemple dans le fichier
-- taille lstEx1 --> 3

-- Pour tester dans elm repl :
$ elm repl
> import Liste exposing (..)
> taille lstEx1
3 : Int

Debug.todo

-- Pendant le développement
maFonction x =
    Debug.todo "À implémenter"

-- Remplacer progressivement les Debug.todo par du vrai code

14. Astuces de Débogage

Afficher des valeurs

-- Pendant le développement
maFonction x =
    let
        _ = Debug.log "x vaut" x  -- Affiche dans la console
    in
    ...

Simplifier le problème

-- Au lieu de résoudre tout de suite [1,2,3,4,5]
-- Teste d'abord avec :
-- []
-- [1]
-- [1, 2]

Écrire la trace

somme [42, 5]
= 42 + somme [5]
     = 5 + somme []
          = 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 - [], [1], [1,2]
  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