disconoscimento
Questo è molto informale, come da lei richiesto.
La grammatica
In un linguaggio tipicamente dipendente abbiamo un raccoglitore sia a livello di tipo che a livello di valore:
Term = * | (∀ (Var : Term). Term) | (Term Term) | (λ Var. Term) | Var
Termine ben digitato è un termine con tipo allegato, scriveremo t ∈ σo
σ
t
per indicare che il termine tha tipo σ.
Regole di battitura
Per semplicità, ciò è necessario in λ v. t ∈ ∀ (v : σ). τentrambi λe ∀associamo la stessa variabile ( vin questo caso).
Regole:
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)
Quindi, *è "il tipo di tutti i tipi" (1), i ∀tipi di forme dai tipi (2), le astrazioni lambda hanno tipi-pi (3) e se vviene introdotto da ∀ (v : σ). τ, allora vha tipo σ(5).
"in forma normale" significa che eseguiamo quante più riduzioni possibili usando la regola di riduzione:
"La" regola di riduzione
(λ 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.
O nella sintassi bidimensionale dove
σ
t
significa t ∈ σ:
(∀ (v : σ). τ) σ SUBS(τ, v, t)
~>
(λ v . b) t SUBS(b, v, t)
È possibile applicare un'astrazione lambda a un termine solo quando il termine ha lo stesso tipo della variabile nel quantificatore forall associato. Quindi riduciamo sia l'astrazione lambda che il quantificatore forall allo stesso modo del calcolo lambda puro prima. Dopo aver sottratto la parte del livello di valore, otteniamo la (4) regola di battitura.
Un esempio
Ecco l'operatore dell'applicazione funzione:
∀ (A : *) (B : A -> *) (f : ∀ (y : A). B y) (x : A). B x
λ A B f x . f x
(abbreviamo ∀ (x : σ). τa σ -> τse τnon fa menzione x)
fritorni B yper qualsiasi fornito ydi tipo A. Applichiamo fa x, che è del tipo giusto A, e sostituire yper xla ∀dopo ., quindi f x ∈ SUBS(B y, y, x)~> f x ∈ B x.
Abbreviamo ora l'operatore dell'applicazione funzione come appe appliciamolo a se stesso:
∀ (A : *) (B : A -> *). ?
λ A B . app ? ? (app A B)
Posto ?per i termini che dobbiamo fornire. Innanzitutto introduciamo e istanziamo esplicitamente Ae B:
∀ (f : ∀ (y : A). B y) (x : A). B x
app A B
Ora dobbiamo unificare ciò che abbiamo
∀ (f : ∀ (y : A). B y) (x : A). B x
che è lo stesso di
(∀ (y : A). B y) -> ∀ (x : A). B x
e ciò che app ? ?riceve
∀ (x : A'). B' x
Questo risulta in
A' ~ ∀ (y : A). B y
B' ~ λ _. ∀ (x : A). B x -- B' ignores its argument
(vedi anche Cos'è la predicatività? )
La nostra espressione (dopo qualche rinominazione) diventa
∀ (A : *) (B : A -> *). ?
λ A B . app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) (app A B)
Dal momento che per qualsiasi A, Be f(dove f ∈ ∀ (y : A). B y)
∀ (y : A). B y
app A B f
possiamo istanziare Ae Bottenere (per chiunque abbia fil tipo appropriato)
∀ (y : ∀ (x : A). B x). ∀ (x : A). B x
app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) f
e la firma del tipo equivale a (∀ (x : A). B x) -> ∀ (x : A). B x.
L'intera espressione è
∀ (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)
ie
∀ (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
che dopo tutto le riduzioni a livello di valore restituiscono lo stesso app.
Così, mentre richiede solo pochi passi nel lambda calcolo puro per ottenere appda app app, in un ambiente tipizzato (e soprattutto un dipendente digitato) abbiamo anche bisogno di preoccuparsi unificazione e le cose diventano più complesse, anche con una certa comodità inconsitent ( * ∈ *).
Tipo di controllo
- Se
tè *quindi t ∈ *di (1)
- Se
tè ∀ (x : σ) τ, σ ∈? *, τ ∈? *(si veda la nota a proposito ∈?qui di seguito) e poi t ∈ *da (2)
- Se
tè f x, f ∈ ∀ (v : σ) τper alcuni σe τ, x ∈? σquindi t ∈ SUBS(τ, v, x)per (4)
- Se
tè una variabile v, è vstato introdotto da ∀ (v : σ). τallora t ∈ σda (5)
Queste sono tutte regole di inferenza, ma non possiamo fare lo stesso per lambdas (l'inferenza di tipo è indecidibile per tipi dipendenti). Quindi per lambdas controlliamo ( t ∈? σ) anziché inferire:
- Se
tè λ v. be verificato contro ∀ (v : σ) τ, b ∈? τallorat ∈ ∀ (v : σ) τ
- Se
tè qualcos'altro e verificato, σquindi dedurre il tipo di tutilizzo della funzione sopra e verificare se lo èσ
Il controllo dell'uguaglianza per i tipi richiede che siano in forme normali, quindi per decidere se tha tipo σcontrolliamo prima che σabbia tipo *. Se è così, allora σè normalizzabile (il paradosso di modulo Girard) e si normalizza (quindi σdiventa ben formato da (0)). SUBSnormalizza anche le espressioni da conservare (0).
Questo si chiama controllo bidirezionale del tipo. Con esso non abbiamo bisogno di annotare ogni lambda con un tipo: se nel f xtipo di fè noto, allora xviene verificato il tipo di argomento fricevuto invece di essere dedotto e confrontato per l'uguaglianza (che è anche meno efficiente). Ma se fè un lambda, richiede un'annotazione esplicita del tipo (le annotazioni sono omesse nella grammatica e ovunque, puoi aggiungere Ann Term Termo λ' (σ : Term) (v : Var)ai costruttori).
Inoltre, dai un'occhiata al più semplice, più facile! post sul blog.