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 :
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 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 !)
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
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
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 :
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 :
4. Conditions avec if-then-else
⚠️ Attention, contrairement à python, les conditions représentent une valeur renvoyée
Syntaxe
⚠️ 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
-- 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
❌ Confondre = et ==
❌ Mauvais pattern matching
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
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 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
É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 - [], [1], [1,2]
- 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