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 t
ha tipo σ
.
Regole di battitura
Per semplicità, ciò è necessario in λ v. t ∈ ∀ (v : σ). τ
entrambi λ
e ∀
associamo la stessa variabile ( v
in 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 v
viene introdotto da ∀ (v : σ). τ
, allora v
ha 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
)
f
ritorni B y
per qualsiasi fornito y
di tipo A
. Applichiamo f
a x
, che è del tipo giusto A
, e sostituire y
per x
la ∀
dopo .
, quindi f x ∈ SUBS(B y, y, x)
~> f x ∈ B x
.
Abbreviamo ora l'operatore dell'applicazione funzione come app
e 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 A
e 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
, B
e f
(dove f ∈ ∀ (y : A). B y
)
∀ (y : A). B y
app A B f
possiamo istanziare A
e B
ottenere (per chiunque abbia f
il 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 app
da 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
, è v
stato 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. b
e verificato contro ∀ (v : σ) τ
, b ∈? τ
allorat ∈ ∀ (v : σ) τ
- Se
t
è qualcos'altro e verificato, σ
quindi dedurre il tipo di t
utilizzo della funzione sopra e verificare se lo èσ
Il controllo dell'uguaglianza per i tipi richiede che siano in forme normali, quindi per decidere se t
ha tipo σ
controlliamo prima che σ
abbia tipo *
. Se è così, allora σ
è normalizzabile (il paradosso di modulo Girard) e si normalizza (quindi σ
diventa ben formato da (0)). SUBS
normalizza 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 x
tipo di f
è noto, allora x
viene verificato il tipo di argomento f
ricevuto 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 Term
o λ' (σ : Term) (v : Var)
ai costruttori).
Inoltre, dai un'occhiata al più semplice, più facile! post sul blog.