# AP2 · 3 — Nommer des valeurs, appeler une fonction

## Notre question

Notre arbre doit maintenant décrire des programmes qui nomment leurs
résultats, puis qui définissent et appellent des fonctions. Prédisez ces deux
programmes du langage cible :

```text
let x = 2.0
{ let x = 10.0 x + 1.0 } + x
```

```text
let increment = fn(x) { x + 1.0 }
increment(4.0)
```

Le premier vaut `13.0` : le `x` du bloc masque temporairement celui de
l'extérieur, et le bloc rend sa dernière expression, `11.0`. Pour savoir ce que
vaut un nom à un endroit du programme, l'interpréteur tient un *environnement*,
c-à-d. la liste des *liaisons* entre un nom et sa valeur visibles à cet endroit.
Le second vaut `5.0`. Une fonction doit garder son corps sans le calculer tout
de suite : la valeur du paramètre n'est connue qu'au moment de l'appel.

> **Repères.** [Slides 28 à 36](https://ap-2026.noce.fr/slides/AP2.html#28) : liaison, environnement, portée, corps différé, appel. Elixir : liste de couples, récursion sur le reste, `length/1`, `Enum.zip/2`, expliqués au point d'emploi. Poly, chap. 3 « Collections et le pipe », chap. 4 et chap. 5, § 5.6 « Noms et environnements » et § 5.7.1 « Une fonction garde un calcul pour plus tard ».

> 💡 Pour une première découverte, commencez par une trace des environnements
> et d'un appel ; l'implémentation pourra venir ensuite, selon notre rythme.
> Vous pouvez lire les exemples sans exécuter les cellules de reprise.
> Notez les liaisons visibles avant, pendant et après le bloc, puis
> les valeurs des arguments et l'environnement du corps d'une fonction.
> **Sauvegardez-le dès maintenant sous `AP2/ap2-03-contexte-fonctions.livemd`.**

## Reprendre votre travail

Ouvrez votre notebook `ap2-02-arbres.livemd` à côté de celui-ci. Une nouvelle
session Livebook ne connaît aucun de vos modules précédents. Copiez vos
cellules **dans les trois cellules indiquées**, puis évaluez-les de haut
en bas. Conservez vos versions dans ce notebook : il ne chargera aucun
fichier ni aucun code sur Internet.

Après la mise en commun, repartez de votre version discutée en classe,
même si elle ne couvre pas encore toutes les extensions. Le cœur de cette
étape utilise des AST écrits à la main et peut avancer sans parser terminé.
Si votre évaluateur bloque encore, construisez d'abord une version limitée
aux cas utilisés ci-dessous ; signalez cette limite dans votre explication.

```elixir
# Copiez ici votre module ArithmeticOps (les opérations sur les valeurs).
```

```elixir
# Copiez ici votre module Interpreter ; vous allez modifier cette cellule.
```

```elixir
# Pour le parcours depuis le texte : copiez vos modules de lecture des littéraux,
# ArithmeticInput et ArithmeticParser. Cette cellule peut attendre le prolongement.
```

Les vérifications de ce notebook utilisent `assert`, fourni par Elixir via
ExUnit : une condition fausse arrête la cellule et affiche la valeur obtenue
à côté de celle attendue. Évaluez la cellule d'import une fois, puis
réévaluez votre code avant ses tests après chaque modification.

```elixir
import ExUnit.Assertions
```

Cette première vérification confirme que votre évaluateur recopié répond encore sur un arbre de l'étape 2 :

```elixir
assert Interpreter.eval({:binary, :plus, {:number, 2.0}, {:number, 3.0}}) == {:ok, 5.0}
```

## Un environnement pour les noms

Nous représentons l'environnement par une liste de couples `{nom, valeur}`,
par ex. `[{"x", 10.0}, {"x", 2.0}]`. La liaison la plus récente est en tête :
ajouter une liaison, c'est construire la nouvelle liste `[{nom, valeur} | env]`,
sans toucher à l'ancienne. Les noms restent des chaînes de caractères ; nous
ne fabriquons pas d'atomes à partir du texte saisi.

Écrivez l'algorithme de `Bindings.fetch/2` : parcourir la liste et rendre la
première liaison qui porte le nom demandé, sinon
`{:error, {:undefined_name, nom}}`. C'est la récursion sur une liste d'AP1,
avec trois cas : la liste vide, une tête dont le nom correspond, une tête qui
ne correspond pas. Pour vos propres tests, choisissez un exemple qui vérifie
le masquage, pas seulement un environnement vide. Ce contrat montre quelle
liaison doit gagner :

```text
iex> Bindings.fetch([{"x", 10.0}, {"x", 2.0}], "x")
{:ok, 10.0}
```

> 💡 Un même nom peut apparaître deux fois dans un motif : la clause
> `fetch([{name, value} | _rest], name)` ne correspond que si le nom de la
> tête est égal au nom cherché. Vous aviez vu cette règle avec `=` en AP1.

```elixir
defmodule Bindings do
  def fetch(_env, _name), do: {:error, :not_implemented}
  # Écrivez les cas du parcours récursif.
end
```

```elixir
assert Bindings.fetch([{"x", 10.0}, {"x", 2.0}], "x") == {:ok, 10.0}
assert Bindings.fetch([{"y", 1.0}, {"x", 2.0}], "x") == {:ok, 2.0}
assert Bindings.fetch([], "absent") == {:error, {:undefined_name, "absent"}}
```

Modifiez votre cellule `Interpreter` pour traiter les nœuds ci-dessous. Gardez
vos clauses arithmétiques et remplacez la clause provisoire `:not_implemented`.
Le cas `eval({:name, name}, env)` délègue à `Bindings.fetch(env, name)` ; écrivez
vous-même le cas de `let` avec les règles suivantes :

| AST | Règle à mettre en œuvre |
|---|---|
| `{:name, name}` | Lire la liaison visible dans l'environnement courant. |
| `{:let, name, value, body}` | Évaluer `value` dans l'environnement courant, puis `body` avec la nouvelle liaison en tête. |

Dans `{:let, name, value, body}`, `name` est une chaîne, `value` le sous-arbre
à calculer, et `body` la suite du programme, c-à-d. tout ce qui est écrit après
le `let`. L'environnement est immuable : l'ajout d'une liaison pour un
sous-arbre ne modifie pas l'environnement de son voisin. Dans
`let x = x + 1.0`, le `x` à droite désigne la liaison précédente, s'il y en a
une : ce `let` n'est pas récursif.

Prédisez les trois résultats ci-dessous, en écrivant l'environnement au moment
où chaque `x` est lu.

```elixir
# let x = 2.0 ; { let x = 10.0 ; x + 1.0 } + x
# Les points-virgules sont uniquement dans ce commentaire, pas dans notre langage.
scoped = {:let, "x", {:number, 2.0},
  {:binary, :plus,
    {:let, "x", {:number, 10.0}, {:binary, :plus, {:name, "x"}, {:number, 1.0}}},
    {:name, "x"}}}
assert Interpreter.eval(scoped) == {:ok, 13.0}
assert Interpreter.eval({:let, "x", {:binary, :plus, {:name, "x"}, {:number, 1.0}},
  {:name, "x"}}, [{"x", 2.0}]) == {:ok, 3.0}
assert Interpreter.eval({:let, "x", {:name, "missing"}, {:number, 1.0}}) ==
  {:error, {:undefined_name, "missing"}}
```

## Différer un corps, puis l'appeler

Ajoutez deux formes à votre évaluateur. À cette étape, **le corps d'une fonction
ne voit que ses paramètres**. Ce choix provisoire permet de faire fonctionner
les fonctions *fermées*, celles dont le corps n'utilise que ses paramètres,
puis de découvrir ce qui manque pour les autres.

| AST | Résultat ou travail à faire |
|---|---|
| `{:fn, parameters, body}` | Créer la valeur `{:function, parameters, body}`, sans évaluer `body`. |
| `{:call, function, arguments}` | Évaluer la fonction, puis les arguments chez l'appelant ; lier les paramètres à ces valeurs et évaluer le corps. |

Distinguons bien le *paramètre*, nom écrit dans la définition (`x` dans
`fn(x) { … }`), de l'*argument*, expression écrite à l'appel (`4.0` dans
`increment(4.0)`). Le nombre de paramètres est l'*arité* de la fonction, et
un appel doit fournir exactement autant d'arguments.

Écrivez l'algorithme d'appel entier. Les arguments et les paramètres sont des
listes, dans l'ordre. Commencez avec un seul paramètre si besoin, puis généralisez.
`Enum.zip(parameters, values)` peut construire leurs liaisons, **après** vérification
des longueurs. Il faut rendre `{:error, {:arity, attendu, reçu}}` si elles diffèrent,
et `{:error, :not_a_function}` si la valeur appelée n'est pas une fonction.
Les erreurs sur les arguments doivent remonter au lieu de devenir des valeurs :
pour évaluer les arguments l'un après l'autre et s'arrêter à la première erreur,
une petite fonction récursive avec accumulateur, comme en AP1, fait l'affaire.

> 💡 `length(liste)` compte les éléments d'une liste ; un garde peut s'en
> servir, par ex. `when length(names) == length(values)`.
> `Enum.zip(["a", "b"], [3, 4])` produit `[{"a", 3}, {"b", 4}]`.
> L'association s'arrête à la liste la plus courte : vérifier les longueurs
> avant l'appel évite de perdre silencieusement un argument.
> Une fonction Elixir anonyme s'écrit `fn x -> x + 1 end` et s'appelle avec
> `f.(4)` ; notre `{:function, paramètres, corps}` est en revanche une donnée,
> que votre interpréteur doit lui-même exécuter.

Avant d'évaluer, prédisez le résultat de chaque ligne ci-dessous. Pourquoi la
définition de `delayed` réussit-elle alors que son corps divise par zéro ?

```elixir
increment = {:fn, ["x"], {:binary, :plus, {:name, "x"}, {:number, 1.0}}}
assert Interpreter.eval({:call, increment, [{:number, 4.0}]}) == {:ok, 5.0}
assert Interpreter.eval({:call, increment, []}) == {:error, {:arity, 1, 0}}
assert Interpreter.eval({:call, {:number, 4.0}, []}) == {:error, :not_a_function}
assert Interpreter.eval({:call, increment, [{:name, "missing"}]}) ==
  {:error, {:undefined_name, "missing"}}
# fn(x) { 1.0 / 0.0 } est une valeur ; l'erreur attend l'appel.
delayed = {:fn, ["x"], {:binary, :divide, {:number, 1.0}, {:number, 0.0}}}
assert {:ok, {:function, _, _}} = Interpreter.eval(delayed)
assert Interpreter.eval({:call, delayed, [{:number, 2.0}]}) == {:error, :division_by_zero}
```

### Le souci qui mène à l'étape suivante

Que devrait donner ce programme ? Que peut donner notre fonction qui ne garde
que ses paramètres et son corps ? Dans le corps de `add_offset`, le nom `offset`
n'est ni un paramètre ni une liaison créée par le corps : c'est une
*variable libre*. Tracez les environnements avant de tester.

```text
let offset = 2.0
let add_offset = fn(x) { offset + x }
add_offset(3.0)
```

```elixir
free_name = {:let, "offset", {:number, 2.0},
  {:let, "add_offset", {:fn, ["x"], {:binary, :plus, {:name, "offset"}, {:name, "x"}}},
    {:call, {:name, "add_offset"}, [{:number, 3.0}]}}}
assert Interpreter.eval(free_name) == {:error, {:undefined_name, "offset"}}
```

## Prolongement depuis le texte

Cette partie peut se poursuivre après la reprise : comprendre et tester la portée
sur les AST est prioritaire. Copiez vos modules de lecture si ce n'est pas encore
fait, puis étendez-les ; aucun parser complet n'est téléchargé pour vous.

1. Dans le lexer, lisez les noms entiers. `Regex.run(~r/^[a-z][a-z0-9_]*/, source)`
   rend `[mot]` si un nom commence la chaîne, `nil` sinon, et
   `String.replace_prefix(source, mot, "")` rend le reste, comme dans
   `DecimalLiteral`. Distinguez ensuite `let` et `fn` des autres mots, par ex.
   avec `case mot do "let" -> :let … _ -> {:name, mot} end` : ainsi `letter`
   reste un nom. Ajoutez les tokens `:assign`, `:lbrace`, `:rbrace`, `:comma`
   pour `=`, `{`, `}`, `,`.
2. Ajoutez la règle `sequence := "let" name "=" expression sequence | expression`.
   Le cas `let` lit une valeur puis la suite, et produit `{:let, nom, valeur, suite}`.
   `parse/1` commence désormais par `sequence`.
3. Étendez `primary` avec un nom, `{ sequence }` et `fn ( paramètres ) { sequence }`.
   Chaque lecteur doit consommer son délimiteur fermant ; refusez les paramètres répétés.
4. Après un primaire, consommez les éventuels `( arguments )` pour construire
   `{:call, fonction, arguments}`. Les arguments sont des expressions séparées par
   des virgules ; `)` ferme la liste. Répétez pour pouvoir appeler une fonction renvoyée.

Le bloc est une expression : son AST est directement celui de sa séquence.
Tracez un petit exemple de `let` avant de généraliser aux fonctions ; vous pouvez
vous partager lecture des paramètres et lecture des arguments.

```elixir
# À lancer lorsque vous avez réalisé le prolongement syntaxique.
for {source, expected} <- [
  {"let x = 2.0 { let x = 10.0 x + 1.0 } + x", 13.0},
  {"let increment = fn(x) { x + 1.0 } increment(4.0)", 5.0}
] do
  {:ok, tokens} = ArithmeticInput.lex(source)
  {:ok, tree} = ArithmeticParser.parse(tokens)
  assert Interpreter.eval(tree) == {:ok, expected}
end
```

## Avant de reprendre les slides

Expliquez pourquoi le `x` extérieur survit au bloc et pourquoi nous ne calculons
pas le corps d'une fonction dès sa définition. Pour `add_offset`, nommez précisément
l'information perdue. Proposez un test personnel qui distingue une fonction d'un
simple résultat de calcul. Retenez que l'environnement est une donnée transmise
aux sous-arbres, jamais modifiée sur place, et qu'une fonction est pour l'instant
un corps en attente de ses arguments.

Écrivez vos réponses dans une cellule Markdown de ce notebook, avec votre
contre-exemple et ce qu'il vous a appris. Sauvegardez ce notebook dans `AP2/`.
Pour le rendu unique du cycle AP1–AP3, je préciserai en cours les étapes
à terminer et le résultat minimum attendu. Les numéros indiquent l'ordre
de progression, pas une liste de devoirs. Les prolongements non demandés
restent facultatifs et peuvent apporter un bonus.
L'échéance commune est le 6 décembre 2026 à 23 h 59 (Paris) ; il n'y a pas
de devoir intermédiaire. AP3 s'ouvrira par un quiz de dix minutes sur
cette séance : relisez le chapitre 5 du poly, « Construire un langage ».

Si un bout de syntaxe Elixir vous manque en route, les chapitres 1 à 4 du
[poly](https://ap-2026.noce.fr/poly/poly-elixir.pdf) se consultent au besoin ; ils ne sont pas un prérequis de cette
étape.

Pour toute question, vous pouvez me contacter par email :
[aurelien.noce@minesparis.psl.eu](mailto:aurelien.noce@minesparis.psl.eu).
