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 teinen Typ hat σ.
Tippregeln
Der Einfachheit halber fordern wir das in λ v. t ∈ ∀ (v : σ). τbeiden λund ∀binden die gleiche Variable ( vin 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 vdurch eingeführt ∀ (v : σ). τ, dann vTyp σ(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)
fGibt B yfür jeden bereitgestellten yTyp zurück A. Wir wenden fauf x, die von der richtigen Art ist A, und Ersatz yfür xdie ∀nach ., so f x ∈ SUBS(B y, y, x)~> f x ∈ B x.
Kürzen wir nun den Funktionsanwendungsoperator als appund 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 Aund 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, Bund f(wobei f ∈ ∀ (y : A). B y)
∀ (y : A). B y
app A B f
wir können instanziieren Aund Bbekommen (für jeden fmit 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 appzurückgibt.
Während es also nur ein paar Schritte in der reinen Lambda-Rechnung erfordert, um appdavon 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
tist *dann t ∈ *von (1)
- Wenn
tist ∀ (x : σ) τ, σ ∈? *, τ ∈? *(siehe die Notiz ∈?unten) dann t ∈ *durch (2)
- Wenn
tist f x, f ∈ ∀ (v : σ) τfür einige σund τ, x ∈? σdann t ∈ SUBS(τ, v, x)durch (4)
- Wenn
tes sich um eine Variable handelt v, vwurde 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
tist λ v. bund gegen geprüft ∀ (v : σ) τ, b ∈? τdannt ∈ ∀ (v : σ) τ
- Wenn
tes sich um etwas anderes handelt und gegen das geprüft σwird, schließen Sie die Art der tVerwendung 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 tder 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). SUBSnormalisiert 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 xder Typ von fbekannt ist, xwird er gegen den Typ des fempfangenen Arguments geprüft, anstatt abgeleitet und auf Gleichheit verglichen zu werden (was ebenfalls weniger effizient ist). Wenn fes 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 Termoder hinzufügen λ' (σ : Term) (v : Var)).
Schauen Sie sich auch den einfacheren an! Blogeintrag.