Inférence de type
-
A
- Typage implicite.
- B
- Inférence de type.
- C
- Unification.
Types implicites
Il est très pénible de devoir écrire
| Fun (x:Nat) -> Fun (y:Nat) -> x+y
|
|
Alors que les types sont ici « évidents ». Si on écrit
Il est clair que l’on peut facilement deviner que
x et y doivent être de type Nat.
On peut ensuite vérifier le typage comme il y a quinze jours.
Typage implicite (à la Curry)
On préfère deviner et vérifier en même temps.
Les règles,
E ⊕ [x:A] ⊢ t:B |
|
E ⊢ (Fun (x:A) -> t):A -> B |
|
| E ⊕ [x:A] ⊢ t:A |
|
E ⊢ (Fix (x:A) -> t):A |
|
|
deviennent
E ⊕ [x:A] ⊢ t:B |
|
E ⊢ (Fun x -> t):A -> B |
|
| E ⊕ [x:A] ⊢ t:A |
|
E ⊢ (Fix x -> t):A |
|
|
Et c’est tout !
Pour être complet
Voici les autres règles qui ne changent pas.
| | E ⊢ t1:Nat E ⊢ t2:Nat |
|
E ⊢ t1 op t2:Nat |
|
|
E ⊢ t1:Nat E ⊢ t2:A E ⊢ t3:A |
|
E ⊢ Ifz t1 Then t2 Else t3:A |
|
|
E ⊢ t1:A -> B E ⊢ t2:A |
|
E ⊢ t1 t2:B |
|
| E ⊢ t1:A E ⊕ [x:A] ⊢ t2:B |
|
E ⊢ Let x = t1 In t2:B |
|
|
En effet, il n’y a rien à deviner ici (cf. le Let).
Une vraie relation
Contrairement au typage explicite (à la Church).
On peut avoir E ⊢ t:A1 et E ⊢ t:A2
pour A1 ≠ A2.
Par ex.
[x:Nat] ⊢ x:Nat |
|
⊢ (Fun x -> x):Nat -> Nat |
|
Et
[x:Nat -> Nat] ⊢ x:Nat -> Nat |
|
⊢ (Fun x -> x):(Nat -> Nat) -> Nat -> Nat |
|
Et à vrai dire, pour tout type A, on a facilement
[x:A] ⊢ x:A |
|
⊢ (Fun x -> x):A -> A |
|
Exemple
Fun x -> x+1 est bien de type Nat -> Nat.
[x:Nat] ⊢ x:Nat
[x:Nat] ⊢ 1:Nat |
|
[x:Nat] ⊢ x+1:Nat |
|
|
⊢ (Fun x -> x+1):Nat -> Nat |
|
|
-
On cherche à typer la fonction.
- On devine le bon type pour x (Nat).
- On cherche à typer x+1.
- On type les arguments de l’addition.
- On vérifie l’addition.
- On construit le type de la fonction.
Mais il y a encore une étape magique : deviner [x:Nat].
Variable de type
Fun x -> x+1 est de type ?
X -> Nat, avec [X=Nat].
[x:X] ⊢ x:X
[x:X] ⊢ 1:Nat |
|
[x:X] ⊢ x+1:Nat, [X=Nat] |
|
|
⊢ (Fun x -> x+1):X -> Nat, [X=Nat] |
|
|
-
On cherche à typer la fonction.
- On pose que le type de x est X
- On cherche à typer l’addition.
- Premier argument, second argument.
- Pour typer d’addition il faut [X=Nat].
- On type la fonction.
Idée générale de l’inférence (synthèse) de type
-
Procéder à un essai de vérification, en
-
Introduisant des variables,
E ⊕ [x:X] ⊢ t:A |
|
E ⊢ (Fun x -> t):X -> A |
|
- Et posant des équations.
E ⊢ t1:A1 E ⊢ t2:A2 |
|
E ⊢ t1 + t2:Nat, [A1 = Nat, A2 = Nat] |
|
- À la fin, on a un type contenant des variables
et des équations.
Retour sur la vérification
Les types sont donc changés, ils peuvent contenir des variables.
| | A1 ∈ T A2 ∈ T |
|
A1 -> A2 ∈ T |
|
|
Avec les nouveaux types on a :
[x:X] ⊢ x:X |
|
⊢ (Fun x -> x):X -> X |
|
Quel sens donner à un tel type ?
id:X -> X veut dire
que id:A -> A pour tout type A.
Et en effet on peut donner les types suivants à Fun x -> x.
-
Nat -> Nat,
(Nat -> Nat) -> Nat -> Nat, etc.
- Et même (Y -> Z) -> Y -> Z,
mais pas Y -> Nat.
Variables dans les types
Sémantique :
Si X apparaît dans A, type de t, alors
[X↦ B]A est aussi un type de t pour tout type B.
Note :
[X↦ B] est une nouvelle notation pour la substitution,
cette fois appliquée aux types.
Rappels/Remarques.
-
Substitution : fonction des variables dans les termes.
- Facilement, étendue aux termes :
σ(A -> B) = σ(A) -> σ(B).
- Substitutions de support fini { X ∣ σ(X) ≠ X }.
- Pas de problèmes de capture ici.
Un sens pour les variables de type
Si X apparaît dans A, type de t, alors
[X↦ B]A est aussi un type de t pour tout type B.
Définition justifiée par le lemme (facile).
Soit σ une substitution, alors
E ⊢ t:A ⇒ σ(E) ⊢ t:σ(A).(Avec bien entendu σ(E) défini comme associant
σ(E(x)) à x.)
Autre exemple d’inférence
[f:F, x:X] ⊢ f:F
[f:F, x:X] ⊢ x:X |
|
[f:F, x:X] ⊢ f x:Y |
|
[f:F, x:X] ⊢ x:X |
|
|
[f:F, x:X] ⊢ f x+x:Nat |
|
|
[f:F] ⊢ (Fun x -> f x+x):X -> Nat |
|
|
⊢ (Fun f -> Fun x -> f x+x):F -> X -> Nat |
|
|
-
On cherche à typer les deux Fun
- On cherche à typer f x+x,
puis l’application f x.
- On a maintenant le type de toutes les feuilles.
- Le type de f x est Y
avec l’équation [F = X -> Y].
- Le type de f x+x est Nat,
avec [Y = Nat, X=Nat].
- Type final, avec les équations
[F = X -> Y,
Y = Nat, X=Nat].
Résultat de l’inférence
Le type :
⊢ (Fun f -> Fun x -> f x+x):F -> X -> Nat
|
Avec les équations :
[F = X -> Y, Y = Nat, X=Nat] |
On résout (en substituant X et Y par leurs valeurs) :
[X ↦ Nat,Y ↦ Nat,
F ↦ Nat -> Nat] |
On substitue dans le type :
⊢ Fun f -> Fun x -> f x+x:(Nat -> Nat) -> Nat -> Nat
|
Nous allons systématiser l’algorithme d’inférence de type.
Production des équations (et du type)
À un environnement de typage E et un terme t, on associe
un type A et un ensemble (une liste, peu importent les doublons)
d’équations.
Avec les notations de définition de prédicat par induction.
| E ⊢ t1 ↝ A1, E1 E ⊢ t2 ↝ A2, E2 |
|
E ⊢ t1 t2 ↝ X, E1 ⋃ E2 ⋃ [A1 = A2 -> X] |
|
(X frais)
|
|
E ⊕ [x:X] ⊢ t ↝ A, E |
|
E ⊢ (Fun x -> t) ↝ X -> A, E |
| (X frais)
|
|
Production des équations II
Les opérations sur les entiers.
| E ⊢ t1 ↝ A1, E1 E ⊢ t2 ↝ A2, E2 |
|
E ⊢ t1 op t2 ↝ Nat, E1 ⋃ E2 ⋃ [A1=Nat, A2=Nat] |
|
|
E ⊢ t1 ↝ A1, E1
E ⊢ t2 ↝ A2, E2
E ⊢ t3 ↝ A3, E3 |
|
E ⊢ Ifz t1 Then t2 Else t3 ↝ A2, E1 ⋃ E2 ⋃ E3 ⋃ [A1=Nat, A2=A3] |
|
|
Équations III
E ⊕ [x:X] ⊢ t ↝ A, E |
|
E ⊢ (Fix x -> t) ↝ A, E ⋃ [X=A] |
| (X frais)
|
|
E ⊢ t1 ↝ A1, E1
E ⊕ [x:A1] ⊢ t2 ↝ A2, E2 |
|
E ⊢ (Let x = t1 In t2) ↝ A2, E1 ⋃ E2 |
|
|
La relative complexité des notations cache un peu que l’on définit en
fait une fonction :
-
Arguments : E et t.
- Résultat : la paire (A,E).
- Technique : induction sur la structure de t.
Exemple
À typer Fun f -> 2+f 1.
Type : F -> Nat.
Équations : [F = (Nat -> Y),
Y=Nat, Nat=Nat].
Solution : [Y↦Nat, F↦(Nat -> Nat)].
Type : (Nat -> Nat) -> Nat.
Résoudre
Équations du type E = { A1=B1, …, An=Bn },
avec un langage de termes simple (sans variables liées).
Une solution : une substitution σ tq, σ(Ai) = σ(Bi).
On note les substitutions :
Et on impose des Xi distincts, tq. Xi n’apparaît dans
aucun Cj (substitution idempotente σ ∘ σ = σ).
Retour sur l’exemple : [F = Nat -> Y,
Y=Nat, Nat=Nat].
Solution facile : [Y↦Nat, F↦(Nat -> Nat)].
Pas de solution
À typer : Fun f -> f 1+f.
Equations : [F=Nat -> Y, Y=Nat, F=Nat].
Aucune valeur possible pour F car
Nat -> Y ≠ Nat pour tout Y.
C’est donc la résolution qui détecte les errreurs,
avec un message du style :
% ocaml
Objective Caml version 3.10.0
# (fun f -> f 1 + f);;
^
This expression has type int -> int
but is here used with type int
Pas de solution
À typer : Fun x -> x x.
Équations : [X = X -> Y].
Aucune valeur possible pour X, car les types sont des termes finis.
La résolution detecte un autre style d’erreur, avec :
% ocaml
Objective Caml version 3.10.0
# (fun x -> x x);;
^
This expression has type 'a -> 'b
but is here used with type 'a
Plusieures solutions
Terme : Fun f -> Fun x -> f x.
Equations : [F= X -> Y].
Une solution φ : [X↦Nat,F↦(Nat -> Nat),
Y↦Nat].
Une autre σ : [F↦X -> Y]
(il reste des variables !).
Quelle solution ? σ car plus générale (principale).
La solution = une solution principale :
φ solution, entraîne il existe ψ avec φ = ψ ∘ σ.
Ici ψ = [X↦Nat, Y↦Nat].
En effet,
ψ(σ(F)) = ψ(X -> Y) = Nat -> Nat
|
|
Plusieures solutions
Terme : Fun b -> Fun x -> Fun y -> Ifz b Then x Else y.
Type : B -> X -> Y -> X.
Equations : [B=Nat, X=Y].
Une solution σ : [B↦Nat, X↦Y].
Une autre solution σ′ : [B↦Nat, Y↦X].
Les deux solutions sont principales :
Produit une solution principale σ d’un système E.
Informellement : par réécriture de l’ensemble E,
jusqu’à ce qu’il soit une substitution (idempotente).
On choisit une équation de E non-encore examinée.
-
A -> B=C -> D, remplacer par [A=C, B=D].
- Nat=Nat ou X=X, supprimer.
- Nat=A -> B (ou symétrique), échouer.
- X=A (ou symétrique), où X apparaît dans A, échouer.
- X=A (ou symétrique) et X n’apparaît pas dans A,
remplacer X par A dans les autres équations.
Terminaison de l’algorithme de Robinson
À l’étape 5. on garde l’équation
X=A qui est maintenant examinée.
La variable X est résolue,
elle n’apparaît plus dans les équations non-examinées.
Soit la paire d’entiers naturels (V(E),|E|).
-
V(E) nombre de variables non-résolues.
- |E| taille des équations.
L’algo termine, car (V(E),|E|) décroit strictement (ordre lexicographique).
-
Cas 1. et 2.
-
V(E) stable (peut décroître si X=X unique équation qui contient X ),
- |E| décroît.
- Cas 5. V(E) décroît.
Correction
L’algorithme produit un système de la forme :
Où les Xi sont deux à deux distincts et n’apparaissent pas dans les Cj.
Autrement dit une substitution (idempotente)
-
Si mgu(E) n’echoue pas, il produit une solution de E.
- Si il existe une solution de E, alors mgu(E) est une solution principale de E.
Algo de synthèse complet
Synthèse du type de t. En deux étapes :
-
Calculer ⊢ t ↝ A, E.
- Puis, résoudre E, ce qui donne σ
Correction de la synthèse
-
(Correction) Si pas d’échec ⊢ t:σ(A).
- (Complétude) Si ⊢ t:B, alors
il existe ψ, tq. B = ψ (σ(A))
(σ(A) type principal).
Le Fix n’a rien de mystérieux
Fix pow -> Fun n -> Ifz n Then 1 Else 2*pow (n-1)
|
|
Soit P type de pow et N type de n.
-
De (n-1), il vient : [N = Nat]
(et aussi [Nat=Nat], mais bon). Type Nat.
- De pow (n-1), il vient :
[P = (Nat -> Y)]. Type Y.
- De 2*pow (n-1), il vient :
[Y = Nat]. Type Nat.
- De Ifz…, il vient :
[N=Nat] (et aussi [Nat=Nat]). Type Nat.
- Le type de Fun n -> ⋯ est :
N -> Nat.
- Le type de Fix pow -> ⋯ est :
N -> Nat,
avec l’équation : [P =(N -> Nat)].
Point fixe
Fix pow -> Fun n -> Ifz n Then 1 Else 2*pow (n-1)
|
|
Équations,
[N = Nat,
P = (Nat -> Y),
Y = Nat,
N=Nat,
P =(N -> Nat)]
|
Type N -> Nat.
Il n’y a pas « trop » d’équations, chaque équation correspond
à une vérification indispensable.
Par ex. la dernière équation contrôle le bon usage de pow
relativement à sa définition.
Fix pow -> Fun n -> Ifz n Then 1 Else 2*pow
|
|
[N=Nat, P=Nat, P=(N
-> Nat)]
|
Résolution
[N = Nat,
P = (Nat -> Y),
Y = Nat,
N=Nat,
P =(N -> Nat)]
|
-
[N↦Nat] et
[P = (Nat -> Y),
Y = Nat,
Nat=Nat,
P =(Nat -> Nat)].
- [N↦Nat, P↦(Nat -> Y)]
et
[Y = Nat,
Nat=Nat,
(Nat -> Y) =(Nat -> Nat)].
- [N↦Nat, P↦(Nat -> Nat), Y↦Nat] et
[Nat=Nat, (Nat -> Nat) =(Nat -> Nat)]
- …
- [N↦Nat, P↦(Nat -> Nat), Y↦Nat]
Type du point fixe :
N -> Nat soit Nat -> Nat.
L’unification de ML
Il y a deux différences importantes.
-
Le polymorphisme.
- L’unification est incrémentale (et en place).
Polymorphisme
Notre système de type ne sait pas typer
Let id = Fun x -> x In
id id
-
id
a le type (principal) X -> X.
- Soit l’équation
[(X -> X) = ((X -> X) -> Y)]
|
Et donc
Qui n’a pas de solution.
Nous verrons cela la semaine prochaine.
Unification en place
Les types sont en fait un seul graphe que la résolution modifie.
Résoudre :
C’est remplacer X par Nat partout.
Possible en une opération sur un graphe :
La résolution immédiate
Au lieu d’accumuler les équations puis de les résoudre.
Les résoudre dès qu’elles sont posées.
Quel intérêt :
détection plus précoce de l’échec, et messages d’erreurs en
rapport avec le source.
# (fun f -> f 1 + f);;
^
This expression has type int -> int
but is here used with type int
Mais aussi plus proche de l’unification en place pratiquée par ML.
Une règle de résolution incrémentale
On avait :
E ⊢ t1 ↝ A1, E1 E ⊢ t2 ↝ A2, E2 |
|
E ⊢ t1 t2 ↝ X, E1 ⋃ E2 ⋃ [A1 = A2 -> X] |
|
(X frais)
|
On a :
E, σ ⊢ t1 ↝ A1, σ1 E, σ1 ⊢ t2 ↝ A2, σ2 |
|
E, σ ⊢ t1 t2 ↝ X, mgu[A1 = (A2 -> X), σ2] |
|
(X frais)
|
La forme générale du jugement est
Le composant σ représente la solution courante des équations « vues ».
Propriété : σ′ étend σ (σ′ = σ″ ∘ σ) et
σ′(E) ⊢ t:σ′(A)
(et même type principal).
L’unification incrémentale
Soit σ la solution d’un problème d’unification.
C’est à dire σ = { X1 ↦ A1, …, Xn ↦ An },
avec :
-
Les Xi sont deux à deux distincts.
- Les Xi n’apparaissent pas dans les Ai.
Et soit une nouvelle équation B = C.
On veut résoudre les équation E = { B = C } ∪ σ.
On pourrait simplement poser mgu(E), mais ça serait dommage
d’oublier que σ est déjà résolu.
Une vue incrémentale de l’unification
Pour résoudre { B = C, X1 ↦ A1, …, Xn ↦ An }.
-
Remplacer les Xi par les Ai dans B et C —
les Xi n’apparaissent plus.
- Résoudre la nouvelle équation : σ′ = { Y1 ↦ B1, …,
Ym ↦ Bm} — les Xi n’apparaissent ni dans les Yj, ni
dans les Bj.
- Remplacer les Yi par les Bi dans les Aj,
ce qui donne les Cj — les Yi n’apparaissent plus.
- Renvoyer { Y1 ↦ B1, …,
Ym ↦ Bm, X1 ↦ C1, …, Xn ↦ Cm }.
Cela s’abrège en mgu[σ(B) = σ(C)] ∘ σ.
La notation ∘ exprimant les étapes 3. et 4. mais de façon
trop générale (quid si Xi = Yj ?).
Toutes les règles
E, σ ⊢ t1 ↝ A, σ1
E ⊕ [x = A], σ ⊢ t2 ↝ B, σ2 |
|
E, σ ⊢ Let x = t1 In t2 ↝ B, σ2 |
|
| |
E ⊕ [x = X], σ ⊢ t ↝ A, σ′ |
|
E, σ ⊢ (Fun x -> t) ↝ X -> A, σ′ |
|
(X frais)
|
| E, σ ⊢ t1 ↝ A, σ1 E, σ1 ⊢ t2 ↝ B, σ2 |
|
E, σ ⊢ t1 t2 ↝ X, mgu[σ2(A) = σ2(B -> X)] ∘ σ2 |
|
(X frais)
|
|
E ⊕ [x = X], σ ⊢ t ↝ A, σ′ |
|
E, σ ⊢ (Fix x -> t) ↝ A, mgu[σ′(X) = σ′(A)] ∘ σ′ |
|
(X frais)
|
|
Vraiment toutes les règles
| E, σ ⊢ t1 ↝ A1, σ1
σ′1 = mgu[σ1(A1) = Nat] ∘ σ1
E, σ′1 ⊢ t2 ↝ A2, σ2
σ′2 = mgu[σ2(A2) = Nat] ∘ σ2 |
|
E, σ ⊢ t1 op t2 ↝ Nat, σ′2 |
|
|
E, σ ⊢ t1 ↝ A1, σ1
σ′1 = mgu[σ1(A1) = Nat] ∘ σ1
E, σ′1 ⊢ t2 ↝ A2, σ2
E, σ2 ⊢ t3 ↝ A3, σ3
σ′3 = mgu[σ3(A2) = σ3(A3)] ∘ σ3 |
|
E, σ ⊢ Ifz t1 Then t2 Else t3 ↝ A2, σ′3 |
|
|
Une fois encore, ces règles expriment une fonction,
dont les
arguments sont E, σ, t,
et dont le résultat est (A,σ′).
Un exemple
Fix pow -> Fun n -> Ifz n Then 1 Else 2*pow (n-1)
|
|
pow est de type P, n est de type N
(E est [x:X, pow:P]).
-
De Ifz, il vient d’abord, σ = [N↦Nat], puis.
-
Pour le Then E, σ ⊢ 1 ↝ Nat, σ.
- Et le Else
-
D’abord E, σ ⊢ 2 ↝ Nat, σ,
- Puis n-1 donne mgu[σ(N)=Nat] ∘ σ qui ne change rien
- Puis surtout (application pow (n-1))
mgu[σ(P) = σ(Nat -> Y)] ∘ σ.
Qui vaut σ′ = [P↦(Nat -> Y),X↦Nat].
- Et enfin le deuxième argument de « * » conduit au calcul de
mgu[σ′(Y) = Nat] ∘ σ′ qui
est
σ″ = [Y↦Nat, P↦(Nat -> Nat),
N↦Nat] |
Nous avons donc le bilan du typage du Ifz
E, id ⊢ (Ifz n Then 1 Else 2*pow (n-1)) ↝ Nat, σ″
|
- Le type de Fun est N -> Nat,
- C’est donc le type de Fix, avec à calculer
mgu[σ″(P) = σ″(N -> Nat)] ∘ σ″
|
L’équation est triviale, et la substitution ne change pas.
-
TP, inférence de type, puis unification.
- La prochaine fois, polymorphisme.
Ce document a été traduit de LATEX par HEVEA