Zrzeczenie się
Jest to bardzo nieformalne, jak prosiłeś.
Gramatyka
W języku zależnym od typu mamy spoiwo na poziomie typu, a także na poziomie wartości:
Term = * | (∀ (Var : Term). Term) | (Term Term) | (λ Var. Term) | Var
Dobrze wpisany termin to termin z załączonym typem, napiszemy t ∈ σ
lub
σ
t
wskazując, że termin t
ma typ σ
.
Zasady pisania
Dla uproszczenia wymagamy tego w λ v. t ∈ ∀ (v : σ). τ
obu przypadkach λ
i ∀
wiążemy tę samą zmienną ( v
w tym przypadku).
Zasady:
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)
Zatem *
„jest typem wszystkich typów” (1), ∀
tworzy typy z typów (2), abstrakcje lambda mają typy pi (3), a jeśli v
wprowadza je ∀ (v : σ). τ
, to v
ma typ σ
(5).
„w normalnej formie” oznacza, że wykonujemy możliwie najwięcej redukcji, stosując zasadę redukcji:
„Reguła redukcji”
(λ 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.
Lub w dwuwymiarowej składni gdzie
σ
t
oznacza t ∈ σ
:
(∀ (v : σ). τ) σ SUBS(τ, v, t)
~>
(λ v . b) t SUBS(b, v, t)
Można zastosować abstrakcję lambda do terminu, gdy termin ma ten sam typ co zmienna w powiązanym kwantyfikatorze. Następnie zmniejszamy zarówno abstrakcję lambda, jak i kwantyfikator forall w taki sam sposób, jak w czystym rachunku lambda wcześniej. Po odjęciu części poziomu wartości otrzymujemy regułę pisania (4).
Przykład
Oto operator aplikacji funkcji:
∀ (A : *) (B : A -> *) (f : ∀ (y : A). B y) (x : A). B x
λ A B f x . f x
(możemy skrócić ∀ (x : σ). τ
do σ -> τ
jeżeli τ
nie wspominając x
)
f
zwraca B y
dla dowolnego podanego y
typu A
. Stosujemy f
się x
, który jest odpowiedni rodzaj A
, a substytutem y
dla x
w ∀
PO .
, więc f x ∈ SUBS(B y, y, x)
~> f x ∈ B x
.
Skróćmy teraz operatora aplikacji funkcji jako app
i zastosujmy go do siebie:
∀ (A : *) (B : A -> *). ?
λ A B . app ? ? (app A B)
Stawiam ?
na warunki, które musimy podać. Najpierw wyraźnie wprowadzamy i tworzymy wystąpienia A
oraz B
:
∀ (f : ∀ (y : A). B y) (x : A). B x
app A B
Teraz musimy ujednolicić to, co mamy
∀ (f : ∀ (y : A). B y) (x : A). B x
który jest taki sam jak
(∀ (y : A). B y) -> ∀ (x : A). B x
i co app ? ?
otrzymuje
∀ (x : A'). B' x
To skutkuje
A' ~ ∀ (y : A). B y
B' ~ λ _. ∀ (x : A). B x -- B' ignores its argument
(patrz także Co to jest predykatywność? )
Nasze wyrażenie (po zmianie nazwy) staje się
∀ (A : *) (B : A -> *). ?
λ A B . app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) (app A B)
Ponieważ dla każdego A
, B
i f
(gdzie f ∈ ∀ (y : A). B y
)
∀ (y : A). B y
app A B f
możemy utworzyć A
i B
uzyskać (dla każdego f
z odpowiednim typem)
∀ (y : ∀ (x : A). B x). ∀ (x : A). B x
app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) f
a podpis typu jest równoważny (∀ (x : A). B x) -> ∀ (x : A). B x
.
Całe wyrażenie brzmi
∀ (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)
To znaczy
∀ (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
co po wszystkich obniżkach na poziomie wartości daje to samo app
.
Więc choć wymaga to zaledwie kilka kroków w czystym rachunkiem lambda, aby uzyskać app
od app app
, w typie ustawienia (a zwłaszcza w sposób zależny od wpisane) musimy także dbać o unifikacji i rzeczy stają się bardziej złożone nawet z pewnym wygody inconsitent ( * ∈ *
).
Sprawdzanie typu
- Jeśli
t
jest *
następnie t ∈ *
przez (1)
- Jeśli
t
jest ∀ (x : σ) τ
, σ ∈? *
, τ ∈? *
(patrz uwaga na temat ∈?
poniżej), a następnie t ∈ *
przez (2)
- Jeśli
t
jest f x
, f ∈ ∀ (v : σ) τ
aby część σ
, a τ
, x ∈? σ
a następnie t ∈ SUBS(τ, v, x)
przez (4)
- Jeśli
t
jest to zmienna v
, v
wprowadzona została ∀ (v : σ). τ
następnie t ∈ σ
przez (5)
Wszystkie są regułami wnioskowania, ale nie możemy zrobić tego samego dla lambdas (wnioskowanie typu jest nierozstrzygalne dla typów zależnych). Więc dla lambdas sprawdzamy ( t ∈? σ
) zamiast wnioskować:
- Jeśli
t
jest λ v. b
i sprawdzone przeciwko ∀ (v : σ) τ
, b ∈? τ
tot ∈ ∀ (v : σ) τ
- Jeśli
t
jest coś innego i sprawdzone w stosunku do σ
tego, wywnioskuj rodzaj t
użycia powyższej funkcji i sprawdź, czy tak jestσ
Sprawdzanie równości typów wymaga, aby były one w normalnej formie, więc aby zdecydować, czy t
ma typ σ
, najpierw sprawdzamy, czy σ
ma typ *
. Jeśli tak, to σ
jest normalizowalny (paradoks modulo Girarda) i normalizuje się (stąd σ
staje się dobrze uformowany przez (0)). SUBS
normalizuje również wyrażenia do zachowania (0).
Nazywa się to dwukierunkowym sprawdzaniem typu. Dzięki niemu nie musimy dodawać adnotacji do każdej lambda typem: jeśli w f x
typie f
jest znany, to x
jest sprawdzany pod kątem rodzaju argumentu f
otrzymywanego zamiast wnioskowania i porównywania pod kątem równości (co jest również mniej wydajne). Ale jeśli f
jest lambda, wymaga wyraźnej adnotacji typu (adnotacje są pomijane w gramatyce i wszędzie można dodać Ann Term Term
lub λ' (σ : Term) (v : Var)
do konstruktorów).
Rzuć też okiem na prostsze, łatwiejsze! post na blogu.