# Mini-typeur · Carnet 1 — Que peut-on savoir sans exécuter ?

## Notre question

Notre interpréteur découvre certaines erreurs en exécutant un programme. Nous
allons examiner l'arbre avant toute exécution. Peut-on décider que `True + 1.0`
n'a pas de sens sans connaître la valeur des calculs intermédiaires ?

> **Repères.** [Slides 6 à 12](https://ap-2026.noce.fr/slides/AP9-typeur.html#6) : règles sur les nombres, booléens et branches. Elixir : une clause par nœud, `with … else` pour expliquer un refus. Poly, chap. 2 et 5.

Vous partez d'arbres d'AP2 écrits à la main ; le squelette du projet fournit
le lexer et le parser pour tester ensuite depuis le texte. À partir du carnet 2, copiez vos propres modules
précédents aux emplacements indiqués, car chaque Livebook a sa propre session.
Réévaluez la cellule entière après avoir modifié un module ; les vérifications
signalent une erreur tant que les fonctions demandées ne sont pas écrites.
Prédisez d'abord leurs résultats. Vous pouvez ajouter des fonctions auxiliaires.

> 💡 Nous choisirons en cours le point de passage selon votre avancement.

```elixir
defmodule Check do
  def equal(actual, expected) do
    if actual != expected do
      raise "Attendu #{inspect(expected)}, obtenu #{inspect(actual)}"
    end
    :ok
  end
  def error({:error, _}), do: :ok
  def error(value), do: raise("Une erreur était attendue : #{inspect(value)}")
end
```

## Distinguer une valeur et une propriété de cette valeur

Un nombre comme `8.0` est une valeur. `Float` désigne son type. Le typeur rend
`Float` pour `8.0 - 3.0`, mais il ne calcule pas `5.0`. L'arbre représente le
programme ; nous pouvons donc lui appliquer une autre interprétation.

Dans le code Elixir, nous représentons les types par `:float` et `:bool`. Nos
premières règles sont les suivantes. Elles expriment un choix de langage : en
particulier, `==` compare seulement deux nombres dans ce cours.

| Expression cible | Condition sur les sous-expressions | Type du résultat |
| --- | --- | --- |
| `8.0` | aucune | `Float` |
| `True` | aucune | `Bool` |
| `-a` | `a : Float` | `Float` |
| `a + b`, `a - b`, `a * b`, `a / b` | `a : Float` et `b : Float` | `Float` |
| `a < b`, `a == b` | `a : Float` et `b : Float` | `Bool` |
| `case c { True -> a False -> b }` | `c : Bool`, et même type pour `a` et `b` | type commun |

### Prédire, puis chercher un contre-exemple

Quels programmes devraient être acceptés ? Pour les autres, désignez la règle
qui échoue : `1.0 + 2.0`, `True + 1.0`, `True == False`, `1.0 / 0.0`,
`case True { True -> 1.0 False -> False }`.

Le dernier programme ne visiterait qu'une branche à l'exécution. Le typeur doit
cependant examiner **les deux branches** : le type de l'expression ne dépend pas
de la valeur de la condition. En revanche, un diviseur nul n'est pas détecté par
ces règles ; `Float` ne dit pas quel flottant on obtiendra.

## Concevoir le premier typeur

> 💡 Pour enchaîner vos vérifications, `with {:ok, type} <- infer(tree),
> :ok <- expect(type, :float), do: {:ok, :float}` décrit le chemin réussi.
> Une valeur qui ne correspond pas à un motif, typiquement `{:error, raison}`,
> est renvoyée immédiatement. Le résultat du bloc n'existe que si toutes
> les étapes ont réussi ; aucun `else` n'est nécessaire pour propager l'erreur.

Une clause montre le cas d'un nombre. Écrivez les autres règles de `infer/1`,
avec des fonctions auxiliaires au besoin. Rendez `{:ok, type}` ou
`{:error, raison}`. Ne calculez jamais la valeur d'une opération : seules les
réponses des sous-arbres vous intéressent.

Représentations : `{:bool, true}`, `{:neg, arbre}`,
`{:binary, :plus, gauche, droite}` et `{:case, condition, oui, non}`. Les autres
opérateurs sont `:minus`, `:multiply`, `:divide`, `:lt` et `:eq`.

```elixir
defmodule MiniTypes do
  def infer({:number, _value}), do: {:ok, :float}
  # Ajoutez ici les autres règles sous forme de clauses.
  def infer(_tree), do: {:error, :a_concevoir}
end
```

## Relier les règles aux observations

Prédisez chaque réponse. Pour une erreur, identifiez le plus petit sous-arbre
qui suffit à expliquer le souci, puis ajoutez un exemple de votre choix.

```elixir
Check.equal(MiniTypes.infer({:number, 8.0}), {:ok, :float})
Check.equal(MiniTypes.infer({:binary, :lt, {:number, 1.0}, {:number, 2.0}}), {:ok, :bool})
Check.equal(MiniTypes.infer({:binary, :divide, {:number, 1.0}, {:number, 0.0}}), {:ok, :float})
Check.error(MiniTypes.infer({:binary, :plus, {:bool, true}, {:number, 1.0}}))
Check.error(MiniTypes.infer({:binary, :eq, {:bool, true}, {:bool, false}}))
Check.error(MiniTypes.infer({:neg, {:bool, true}}))
Check.error(MiniTypes.infer({:case, {:bool, true}, {:number, 1.0}, {:bool, false}}))
Check.error(MiniTypes.infer({:case, {:number, 0.0}, {:number, 1.0}, {:number, 2.0}}))
Check.equal(MiniTypes.infer({:case, {:bool, false}, {:bool, true}, {:bool, false}}), {:ok, :bool})
```

## Indices

**Premier indice.** Le type d'une feuille se lit directement. Celui d'un nœud
s'obtient en appliquant une règle aux types de ses enfants.

**Deuxième indice.** Une fonction auxiliaire peut comparer un type obtenu à un
type attendu, puis rendre `:ok` ou une erreur. Le `with` d'Elixir permet d'arrêter
le parcours à la première erreur.

**Troisième indice.** Pour `case`, vérifiez successivement la condition, les
deux branches, puis leur accord. L'ordre facilite le diagnostic ; il ne signifie
pas que le typeur exécute ces branches.

Expliquez ce que ce système garantit et ce qu'il ne garantit pas. Si vous avancez
vite, ajoutez à vos erreurs un chemin dans l'arbre, par ex. « branche fausse,
opérande gauche ». Proposez ensuite un autre choix de règle pour `==` et discutez
les programmes qu'il accepterait.

## Avant de passer au carnet suivant

Conservez votre tentative, un résultat prédit puis observé et une explication
avec vos mots. Au carnet suivant, vous copierez votre propre code dans les
cellules indiquées ; aucun module n'est importé implicitement d'un autre notebook.

Vous pouvez reporter vos règles dans le squelette `mini-typeur` (`lib/`),
puis utiliser ses tests de paliers : `mix test`, `mix test --include argent`,
`mix test --include argent --include or`. Le squelette et les notebooks ont des noms de modules
différents : adaptez les appels plutôt que de copier un fichier sans le relire.

Ces carnets préparent le sujet Types du projet noté d’AP9, hors du rendu AP1–AP3.
Le projet commence en semi-autonomie et se termine à la maison. Les supports
restent des bases à affiner d’ici janvier ; le périmètre, le barème et l’échéance
seront précisés avant le lancement. D’ici là, gardez vos essais dans
`entrainement/`. Pour toute question :
[aurelien.noce@minesparis.psl.eu](mailto:aurelien.noce@minesparis.psl.eu).
