Avertissement
C'est très informel, comme vous l'avez demandé.
La grammaire
Dans un langage typé de manière dépendante, nous avons un classeur au niveau du type ainsi qu'au niveau de la valeur:
Term = * | (∀ (Var : Term). Term) | (Term Term) | (λ Var. Term) | Var
Un terme bien typé est un terme avec un type attaché, nous écrirons t ∈ σ
ou
σ
t
pour indiquer que le terme t
a un type σ
.
Règles de frappe
Par souci de simplicité, nous exigeons que dans les λ v. t ∈ ∀ (v : σ). τ
deux λ
et ∀
lier la même variable ( v
dans ce cas).
Règles:
t ∈ σ is well-formed if σ ∈ * and t is in normal form (0)
* ∈ * (1)
∀ (v : σ). τ ∈ * -: σ ∈ *, τ ∈ * (2)
λ v. t ∈ ∀ (v : σ). τ -: t ∈ τ (3)
f x ∈ SUBS(τ, v, x) -: f ∈ ∀ (v : σ). τ, x ∈ σ (4)
v ∈ σ -: v was introduced by ∀ (v : σ). τ (5)
Ainsi, *
est "le type de tous les types" (1), ∀
forme les types à partir des types (2), les abstractions lambda ont des pi-types (3) et si v
est introduit par ∀ (v : σ). τ
, alors v
a le type σ
(5).
"sous forme normale" signifie que nous effectuons autant de réductions que possible en utilisant la règle de réduction:
"La" règle de réduction
(λ v. b ∈ ∀ (v : σ). τ) (t ∈ σ) ~> SUBS(b, v, t) ∈ SUBS(τ, v, t)
where `SUBS` replaces all occurrences of `v`
by `t` in `τ` and `b`, avoiding name capture.
Ou en syntaxe bidimensionnelle où
σ
t
signifie t ∈ σ
:
(∀ (v : σ). τ) σ SUBS(τ, v, t)
~>
(λ v . b) t SUBS(b, v, t)
Il n'est possible d'appliquer une abstraction lambda à un terme que lorsque le terme a le même type que la variable dans le quantificateur forall associé. Ensuite, nous réduisons à la fois l'abstraction lambda et le quantificateur forall de la même manière que dans le calcul lambda pur auparavant. Après avoir soustrait la partie du niveau de valeur, nous obtenons la règle de typage (4).
Un exemple
Voici l'opérateur d'application de fonction:
∀ (A : *) (B : A -> *) (f : ∀ (y : A). B y) (x : A). B x
λ A B f x . f x
(nous abrégeons ∀ (x : σ). τ
à σ -> τ
si τ
ne mentionne pas x
)
f
renvoie B y
pour tout y
type fourni A
. Nous appliquons f
à x
, qui est du type droit A
et substituer y
à x
la ∀
suite .
, donc f x ∈ SUBS(B y, y, x)
~> f x ∈ B x
.
Abrévions maintenant l'opérateur d'application de fonction as app
et appliquons-le à lui-même:
∀ (A : *) (B : A -> *). ?
λ A B . app ? ? (app A B)
Je place ?
pour les termes que nous devons fournir. D'abord, nous introduisons et instancions explicitement A
et B
:
∀ (f : ∀ (y : A). B y) (x : A). B x
app A B
Maintenant, nous devons unifier ce que nous avons
∀ (f : ∀ (y : A). B y) (x : A). B x
ce qui est le même que
(∀ (y : A). B y) -> ∀ (x : A). B x
et ce qui app ? ?
reçoit
∀ (x : A'). B' x
Il en résulte
A' ~ ∀ (y : A). B y
B' ~ λ _. ∀ (x : A). B x -- B' ignores its argument
(voir aussi Qu'est-ce que la prédicativité? )
Notre expression (après quelques renommage) devient
∀ (A : *) (B : A -> *). ?
λ A B . app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) (app A B)
Depuis pour tout A
, B
et f
(où f ∈ ∀ (y : A). B y
)
∀ (y : A). B y
app A B f
nous pouvons instancier A
et B
obtenir (pour tout f
type approprié)
∀ (y : ∀ (x : A). B x). ∀ (x : A). B x
app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) f
et la signature de type est équivalente à (∀ (x : A). B x) -> ∀ (x : A). B x
.
L'expression entière est
∀ (A : *) (B : A -> *). (∀ (x : A). B x) -> ∀ (x : A). B x
λ A B . app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) (app A B)
C'est à dire
∀ (A : *) (B : A -> *) (f : ∀ (x : A). B x) (x : A). B x
λ A B f x .
app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) (app A B) f x
qui, après toutes les réductions au niveau de la valeur, donne le même app
retour.
Ainsi , alors qu'il ne nécessite que quelques étapes du calcul pur lambda pour obtenir app
de app app
, dans un cadre typé (et surtout un dactylographiées dépendamment) nous avons aussi besoin de se soucier de l' unification et les choses deviennent encore plus complexes avec une certaine commodité inconsitent ( * ∈ *
).
Vérification de type
- Si
t
est *
alors t ∈ *
par (1)
- Si
t
est ∀ (x : σ) τ
, σ ∈? *
, τ ∈? *
(voir la note sur ∈?
ci - dessous) puis t ∈ *
par (2)
- Si
t
est f x
, f ∈ ∀ (v : σ) τ
pour certains σ
et τ
, x ∈? σ
alors t ∈ SUBS(τ, v, x)
par (4)
- Si
t
est une variable v
, a v
été introduit d'ici ∀ (v : σ). τ
là t ∈ σ
par (5)
Ce sont toutes des règles d'inférence, mais nous ne pouvons pas faire de même pour les lambdas (l'inférence de type est indécidable pour les types dépendants). Donc, pour les lambdas, nous vérifions ( t ∈? σ
) plutôt que de déduire:
- Si
t
est λ v. b
et vérifié contre ∀ (v : σ) τ
, b ∈? τ
puist ∈ ∀ (v : σ) τ
- Si
t
c'est autre chose et vérifié, σ
déduisez le type d' t
utilisation de la fonction ci-dessus et vérifiez si elle estσ
La vérification de l'égalité pour les types nécessite qu'ils soient sous des formes normales, donc pour décider si t
a du type, σ
nous vérifions d'abord qu'il σ
a du type *
. Si c'est le cas, alors il σ
est normalisable (paradoxe de Modulo Girard) et il se normalise (donc σ
devient bien formé par (0)). SUBS
normalise également les expressions pour conserver (0).
C'est ce qu'on appelle la vérification de type bidirectionnelle. Avec lui, nous n'avons pas besoin d'annoter chaque lambda avec un type: si f x
le type de f
est connu, il x
est f
comparé au type d'argument reçu au lieu d'être déduit et comparé pour l'égalité (ce qui est également moins efficace). Mais s'il f
s'agit d'un lambda, il nécessite une annotation de type explicite (les annotations sont omises dans la grammaire et partout, vous pouvez soit ajouter Ann Term Term
soit λ' (σ : Term) (v : Var)
aux constructeurs).
Jetez également un œil à Simpler, Easier! blogpost.