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 ta 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 ( vdans 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 vest introduit par ∀ (v : σ). τ, alors va 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)
frenvoie B ypour tout ytype fourni A. Nous appliquons fà x, qui est du type droit Aet substituer yà xla ∀suite ., donc f x ∈ SUBS(B y, y, x)~> f x ∈ B x.
Abrévions maintenant l'opérateur d'application de fonction as appet 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 Aet 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, Bet f(où f ∈ ∀ (y : A). B y)
∀ (y : A). B y
app A B f
nous pouvons instancier Aet Bobtenir (pour tout ftype 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 appretour.
Ainsi , alors qu'il ne nécessite que quelques étapes du calcul pur lambda pour obtenir appde 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
test *alors t ∈ *par (1)
- Si
test ∀ (x : σ) τ, σ ∈? *, τ ∈? *(voir la note sur ∈?ci - dessous) puis t ∈ *par (2)
- Si
test f x, f ∈ ∀ (v : σ) τpour certains σet τ, x ∈? σalors t ∈ SUBS(τ, v, x)par (4)
- Si
test 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
test λ v. bet vérifié contre ∀ (v : σ) τ, b ∈? τpuist ∈ ∀ (v : σ) τ
- Si
tc'est autre chose et vérifié, σdéduisez le type d' tutilisation 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 ta 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)). SUBSnormalise é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 xle type de fest connu, il xest fcomparé 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 fs'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 Termsoit λ' (σ : Term) (v : Var)aux constructeurs).
Jetez également un œil à Simpler, Easier! blogpost.