Haftungsausschluss
Dies ist sehr informell, wie Sie es gewünscht haben.
Die Grammatik
In einer abhängig getippten Sprache haben wir einen Ordner sowohl auf der Typebene als auch auf der Wertebene:
Term = * | (∀ (Var : Term). Term) | (Term Term) | (λ Var. Term) | Var
Ein gut typisierter Begriff ist ein Begriff mit angehängter Schrift, den wir t ∈ σ
oder schreiben werden
σ
t
um anzuzeigen, dass der Begriff t
einen Typ hat σ
.
Tippregeln
Der Einfachheit halber fordern wir das in λ v. t ∈ ∀ (v : σ). τ
beiden λ
und ∀
binden die gleiche Variable ( v
in diesem Fall).
Regeln:
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)
Ist *
also "der Typ aller Typen" (1), ∀
bildet Typen aus Typen (2), Lambda-Abstraktionen haben pi-Typen (3) und wenn v
durch eingeführt ∀ (v : σ). τ
, dann v
Typ σ
(5).
"in normaler Form" bedeutet, dass wir so viele Reduzierungen wie möglich mit der Reduzierungsregel durchführen:
"Die" Reduktionsregel
(λ 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.
Oder in zweidimensionaler Syntax wo
σ
t
bedeutet t ∈ σ
:
(∀ (v : σ). τ) σ SUBS(τ, v, t)
~>
(λ v . b) t SUBS(b, v, t)
Es ist nur möglich, eine Lambda-Abstraktion auf einen Term anzuwenden, wenn der Term den gleichen Typ wie die Variable im zugeordneten forall-Quantifizierer hat. Dann reduzieren wir sowohl die Lambda-Abstraktion als auch den Forall-Quantifikator auf die gleiche Weise wie zuvor im reinen Lambda-Kalkül. Nach dem Subtrahieren des Wertebenenteils erhalten wir die Typisierungsregel (4).
Ein Beispiel
Hier ist der Funktionsanwendungsoperator:
∀ (A : *) (B : A -> *) (f : ∀ (y : A). B y) (x : A). B x
λ A B f x . f x
(wir kürzen ∀ (x : σ). τ
auf σ -> τ
wenn τ
nicht erwähnt x
)
f
Gibt B y
für jeden bereitgestellten y
Typ zurück A
. Wir wenden f
auf x
, die von der richtigen Art ist A
, und Ersatz y
für x
die ∀
nach .
, so f x ∈ SUBS(B y, y, x)
~> f x ∈ B x
.
Kürzen wir nun den Funktionsanwendungsoperator als app
und wenden ihn auf sich selbst an:
∀ (A : *) (B : A -> *). ?
λ A B . app ? ? (app A B)
Ich stelle ?
Begriffe auf, die wir liefern müssen. Zuerst führen wir explizit ein und instanziieren A
und B
:
∀ (f : ∀ (y : A). B y) (x : A). B x
app A B
Jetzt müssen wir vereinheitlichen, was wir haben
∀ (f : ∀ (y : A). B y) (x : A). B x
das ist das gleiche wie
(∀ (y : A). B y) -> ∀ (x : A). B x
und was app ? ?
erhält
∀ (x : A'). B' x
Das führt zu
A' ~ ∀ (y : A). B y
B' ~ λ _. ∀ (x : A). B x -- B' ignores its argument
(Siehe auch Was ist Prädikativität? )
Unser Ausdruck (nach einigem Umbenennen) wird
∀ (A : *) (B : A -> *). ?
λ A B . app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) (app A B)
Da für jeden A
, B
und f
(wobei f ∈ ∀ (y : A). B y
)
∀ (y : A). B y
app A B f
wir können instanziieren A
und B
bekommen (für jeden f
mit dem passenden Typ)
∀ (y : ∀ (x : A). B x). ∀ (x : A). B x
app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) f
und die Typensignatur ist äquivalent zu (∀ (x : A). B x) -> ∀ (x : A). B x
.
Der ganze Ausdruck ist
∀ (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)
Dh
∀ (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
was nach allen Reduzierungen auf dem Wertniveau das gleiche app
zurückgibt.
Während es also nur ein paar Schritte in der reinen Lambda-Rechnung erfordert, um app
davon zu kommen app app
, müssen wir uns in einer getippten Umgebung (und insbesondere einer abhängig getippten) auch um die Vereinheitlichung kümmern und die Dinge werden komplexer, selbst mit einigem inkonsequenten Komfort ( * ∈ *
).
Typüberprüfung
- Wenn
t
ist *
dann t ∈ *
von (1)
- Wenn
t
ist ∀ (x : σ) τ
, σ ∈? *
, τ ∈? *
(siehe die Notiz ∈?
unten) dann t ∈ *
durch (2)
- Wenn
t
ist f x
, f ∈ ∀ (v : σ) τ
für einige σ
und τ
, x ∈? σ
dann t ∈ SUBS(τ, v, x)
durch (4)
- Wenn
t
es sich um eine Variable handelt v
, v
wurde bis ∀ (v : σ). τ
dahin t ∈ σ
durch (5) eingeführt.
Dies sind alles Inferenzregeln, aber wir können nicht dasselbe für Lambdas tun (Typinferenz ist für abhängige Typen unentscheidbar). Für Lambdas prüfen wir also ( t ∈? σ
), anstatt daraus zu schließen:
- Wenn
t
ist λ v. b
und gegen geprüft ∀ (v : σ) τ
, b ∈? τ
dannt ∈ ∀ (v : σ) τ
- Wenn
t
es sich um etwas anderes handelt und gegen das geprüft σ
wird, schließen Sie die Art der t
Verwendung der obigen Funktion ab und prüfen Sie, ob dies der Fall istσ
Die Gleichheitsprüfung für Typen erfordert, dass sie in normalen Formen vorliegen. Um zu entscheiden, ob t
der Typ vorhanden ist σ
, überprüfen wir zunächst , ob der Typ vorhanden σ
ist *
. Wenn ja, dann σ
ist es normalisierbar (Modulo-Girard-Paradoxon) und es wird normalisiert (daher σ
wird es durch (0) gut geformt). SUBS
normalisiert auch zu bewahrende Ausdrücke (0).
Dies wird als bidirektionale Typprüfung bezeichnet. Damit müssen wir nicht jedes Lambda mit einem Typ versehen: Wenn f x
der Typ von f
bekannt ist, x
wird er gegen den Typ des f
empfangenen Arguments geprüft, anstatt abgeleitet und auf Gleichheit verglichen zu werden (was ebenfalls weniger effizient ist). Wenn f
es sich jedoch um ein Lambda handelt, ist eine explizite Typanmerkung erforderlich (Anmerkungen werden in der Grammatik weggelassen, und Sie können sie überall entweder den Konstruktoren hinzufügen Ann Term Term
oder hinzufügen λ' (σ : Term) (v : Var)
).
Schauen Sie sich auch den einfacheren an! Blogeintrag.