9 exercices pour ajouter variables, fonctions et listes à votre langage
Laurent Thiry — Programmation en C
« Une variable sans environnement, c'est comme un poisson sans vélo.
Ça peut exister mais c'est pas très utile. »
let — lier des variablesset! — muter l'environnementlambda — créer des fermetures lexicalescar, cdr, cons, null?Reprenez l'évaluateur du TD1 et étendez-le pour gérer les sous-expressions arbitrairement imbriquées.
parseur.c (TD1 ex5–6)eval traite récursivement chaque argument+, -, *, / avec strcmp(* (+ 2 3) (- 10 4)) → 30(+ 1 "hello") ?"Erreur de type" si un argument n'est pas un nombreneg (négation) et abs (valeur absolue)(neg 5) → -5, (abs -3) → 3long long eval(Sexp *e) { if (e->type == SEXPR_INT) return e->entier; if (e->type != SEXPR_CONS || e->car->type != SEXPR_SYM) { fprintf(stderr, "Erreur de syntaxe\n"); return 0; } char *op = e->car->symbole; Sexp *args = e->cdr; /* c) unaires */ if (!strcmp(op, "neg")) return -eval(args->car); if (!strcmp(op, "abs")) { long long v = eval(args->car); return v < 0 ? -v : v; } long long val = eval(args->car); /* b) vérification de type */ if (args->car->type != SEXPR_INT && args->car->type != SEXPR_CONS) { fprintf(stderr, "Erreur de type\n"); return 0; } for (args = args->cdr; args; args = args->cdr) { long long v = eval(args->car); if (!strcmp(op, "+")) val += v; else if (!strcmp(op, "-")) val -= v; else if (!strcmp(op, "*")) val *= v; else if (!strcmp(op, "/")) { if (v == 0) { fprintf(stderr, "/0 !\n"); return 0; } val /= v; } } return val; }
Créez une structure pour stocker des associations nom → valeur.
(x → 10, y → 20)void env_bind(Env *env, Sexp *sym, Sexp *val)typedef struct Env { Sexp *bindings; struct Env *parent; } Env; Env *env_new(Env *parent) { Env *e = malloc(sizeof(Env)); e->bindings = NULL; e->parent = parent; return e; } /* c) ajouter un binding */ void env_bind(Env *env, Sexp *sym, Sexp *val) { env->bindings = sexp_cons( sexp_cons(sym, val), env->bindings); } /* a) + b) lookup avec chaîne */ long long env_lookup(Sexp *sym, Env *env) { while (env) { Sexp *pair = cherche(sym, env->bindings); if (pair) return pair->cdr->entier; env = env->parent; } fprintf(stderr, "Variable %s inconnue\n", sym->symbole); return 0; }
let — lier des variables ★★☆Ajoutez la forme spéciale (let nom valeur) à votre évaluateur.
eval, si l'opérateur est "let", traitez-le spécialement(let x 42) (+ x 1) → 43(let x (+ 2 3)) — il faut évaluer la valeur avant de lier(let x (+ 2 3)) (* x 2) → 10(let* (x 1) (y (+ x 1)) (z (+ y 1)))if (!strcmp(op, "let")) { /* a) let simple */ Sexp *sym = args->car; Sexp *val_expr = args->cdr->car; long long v = eval(val_expr, env); /* b) évalue d'abord */ env_bind(env, sym, sexp_int(v)); return v; } if (!strcmp(op, "let*")) { /* c) let* : chaque binding voit les précédents */ Sexp *bindings = args; long long last = 0; while (bindings) { Sexp *sym = bindings->car->car; Sexp *val_expr = bindings->car->cdr->car; last = eval(val_expr, env); env_bind(env, sym, sexp_int(last)); bindings = bindings->cdr; } return last; }
set! — mutation ★★☆Ajoutez (set! nom nouvelle_valeur) pour modifier un binding existant.
(let x 10) (set! x 42) x → 42let et un enfantset! dans l'enfant modifie la variable du parent(let x 10) (let f (lambda () (set! x 20))) (f) x → 20cell et cell-ref (voir TD3) pour simuler une mutation(let c (cell 5)) (cell-ref c) → 5if (!strcmp(op, "set!")) { Sexp *sym = args->car; Sexp *val_expr = args->cdr->car; long long v = eval(val_expr, env); Env *e = env; while (e) { /* b) remonte la chaîne */ Sexp *pair = cherche(sym, e->bindings); if (pair) { sexp_free(pair->cdr); pair->cdr = sexp_int(v); return v; } e = e->parent; } fprintf(stderr, "set! : %s non trouvé\n", sym->symbole); return 0; }
let crée un nouveau binding, set! modifie un existant. C'est la différence entre écrire dans un cahier neuf et corriger une faute dans un cahier déjà rempli.lambda — fermetures lexicales ★★★Créez des fonctions anonymes qui capturent l'environnement courant.
(fun (x) (* x x) . env_snapshot)apply_closure : crée un nouvel env, lie params→args, évalue le corps(let carre (lambda (x) (* x x))) (carre 5) → 25(map (lambda (x) (* x 2)) '(1 2 3))make-compteur qui renvoie une closure avec une variable internestatic Sexp *make_closure(Sexp *params, Sexp *body, Env *env) { Sexp *snap = NULL; for (Env *e = env; e; e = e->parent) snap = sexp_cons(e->bindings ? sexp_copie(e->bindings) : NULL, snap); return sexp_cons(sexp_sym("fun"), sexp_cons(params, sexp_cons(body, snap))); } if (!strcmp(op, "lambda")) { return make_closure(args->car, args->cdr->car, env); } if (fn->type == SEXPR_CONS && fn->car->type == SEXPR_SYM && !strcmp(fn->car->symbole, "fun")) { return apply_closure(fn, args, env); } /* c) make-compteur en LISP */ ;; (let make-compteur ;; (lambda () ;; (let compteur 0) ;; (lambda () (set! compteur (+ compteur 1)) compteur)))
Ajoutez les primitives de manipulation de listes.
(car '(1 2 3)) → retourne le premier élément(cdr '(1 2 3)) → retourne le reste(cons 1 '(2 3)) → construit une nouvelle paire(null? '(1 2)) → faux, (null? '()) → vrailength : (length '(1 2 3)) → 3append : (append '(1 2) '(3 4)) → (1 2 3 4)reverse : (reverse '(1 2 3)) → (3 2 1)nth : (nth 1 '(a b c)) → b (indexation 0-based)/* a) primitives */ if (!strcmp(op, "car")) { Sexp *lst = eval(args->car, env); return lst ?& lst->car : NULL; } if (!strcmp(op, "cdr")) { Sexp *lst = eval(args->car, env); return lst ?& lst->cdr : NULL; } if (!strcmp(op, "cons")) { return sexp_cons(eval(args->car, env), eval(args->cdr->car, env)); } if (!strcmp(op, "null?")) { Sexp *v = eval(args->car, env); return sexp_int(v == NULL || (v->type == SEXPR_CONS && v->car == NULL)); } /* b) length, append en C (ou en LISP, au choix) */ if (!strcmp(op, "length")) { Sexp *lst = eval(args->car, env); int n = 0; while (lst && lst->type == SEXPR_CONS) { n++; lst = lst->cdr; } return sexp_int(n); } /* c) reverse : itératif */ if (!strcmp(op, "reverse")) { Sexp *lst = eval(args->car, env); Sexp *res = NULL; while (lst && lst->type == SEXPR_CONS) { res = sexp_cons(lst->car, res); lst = lst->cdr; } return res; }
'(1 2 3) est du sucre syntaxique pour (quote (1 2 3)). sexp_parse le gère déjà. Si car est "quote", retournez args->car sans l'évaluer.Écrivez des fonctions LISP qui manipulent des listes.
(map f '(1 2 3)) en LISP(map (lambda (x) (* x x)) '(1 2 3 4)) → (1 4 9 16)filter : garde les éléments qui satisfont un prédicat(filter (lambda (x) (> x 2)) '(1 2 3 4)) → (3 4)foldl : (foldl + 0 '(1 2 3)) → 6sum et product;; a) map (let map (lambda (f lst) (if (null? lst) '() (cons (f (car lst)) (map f (cdr lst)))))) ;; b) filter (let filter (lambda (pred lst) (if (null? lst) '() (if (pred (car lst)) (cons (car lst) (filter pred (cdr lst))) (filter pred (cdr lst)))))) ;; c) foldl (let foldl (lambda (f acc lst) (if (null? lst) acc (foldl f (f acc (car lst)) (cdr lst))))) (let sum (lambda (lst) (foldl + 0 lst))) (let product (lambda (lst) (foldl * 1 lst)))
Défis supplémentaires pour enrichir votre langage. Solutions fournies.
Ajoutez (function nom (params) corps) qui crée une fonction récursive en liant nom dans l'environnement de la fermeture.
(function fact (n) (if (= n 1) 1 (* n (fact (- n 1)))))
(begin expr1 expr2 ...) évalue chaque expression et retourne la dernière.
(if cond then else) — n'évaluer que la branche choisie.
Testez : (if (> 3 2) 42 0) → 42
Comparaison de symboles ou d'entiers.
(eq? 'a 'a) → vrai, (eq? 42 42) → vrai
Opérateurs logiques, avec court-circuit.
(and (> 3 2) (< 5 10)) → 1 (vrai)
;; function récursive (let fact (function fact (n) (if (eq? n 1) 1 (* n (fact (- n 1)))))) ;; if dans eval : if (!strcmp(op, "if")) { long long cond = eval(args->car, env); Sexp *branch = cond ? args->cdr->car : args->cdr->cdr->car; return eval(branch, env); } ;; begin if (!strcmp(op, "begin")) { Sexp *cur = args; long long last = 0; while (cur) { last = eval(cur->car, env); cur = cur->cdr; } return last; }
if paresseux, c'est comme un étudiant le lundi matin : il n'évalue que ce qui est vraiment nécessaire et ignore le reste.let.c si vous êtes bloqué.Implémentez quicksort dans ./let en utilisant uniquement des fonctions récursives (pas de boucle).
Algorithme :
Indice
Implémentez (letrec bindings corps) qui permet des définitions récursives et mutuellement récursives.
Contrairement à let, letrec évalue les bindings dans un environnement où tous les noms sont déjà visibles.
(letrec ((pair (cons 1 2))) pair)
getchar/putchar.
Votre évaluateur sait maintenant : lier des variables (let),
les modifier (set!), créer des fonctions (lambda),
et manipuler des listes (car, cdr, cons).