отказ
Это очень неформально, как вы и просили.
Грамматика
В языке с зависимой типизацией у нас есть связующее на уровне типов, а также на уровне значений:
Term = * | (∀ (Var : Term). Term) | (Term Term) | (λ Var. Term) | Var
Хорошо введенный термин - это термин с прикрепленным типом, мы напишем t ∈ σ
или
σ
t
чтобы указать, что термин t
имеет тип σ
.
Правила печатания
Для простоты мы требуем, чтобы λ v. t ∈ ∀ (v : σ). τ
оба λ
и ∀
связывали одну и ту же переменную ( v
в данном случае).
Правила:
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)
Таким образом, *
это «тип всех типов» (1), ∀
образует типы из типов (2), лямбда-абстракции имеют pi-типы (3) и, если они v
представлены ∀ (v : σ). τ
, то v
имеют тип σ
(5).
«в нормальной форме» означает, что мы выполняем как можно больше сокращений, используя правило сокращения:
Правило сокращения
(λ 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.
Или в двумерном синтаксисе, где
σ
t
означает t ∈ σ
:
(∀ (v : σ). τ) σ SUBS(τ, v, t)
~>
(λ v . b) t SUBS(b, v, t)
Лямбда-абстракцию можно применять только к термину, если термин имеет тот же тип, что и переменная в связанном квантификаторе поиска. Затем мы уменьшаем как лямбда-абстракцию, так и квантификатор Форалла таким же образом, как в чистом лямбда-исчислении ранее. После вычитания части уровня значения мы получаем (4) правило набора текста.
Пример
Вот оператор приложения функции:
∀ (A : *) (B : A -> *) (f : ∀ (y : A). B y) (x : A). B x
λ A B f x . f x
(мы сокращаем , ∀ (x : σ). τ
чтобы σ -> τ
если τ
не упоминать x
)
f
возвращает B y
для любого предоставленного y
типа A
. Применим f
к x
, что правильный тип A
, и заменить y
на x
в ∀
AFTER .
, таким образом , f x ∈ SUBS(B y, y, x)
~> f x ∈ B x
.
Давайте теперь сокращаем оператор приложения функции как app
и применяем его к себе:
∀ (A : *) (B : A -> *). ?
λ A B . app ? ? (app A B)
Я ставлю ?
на условия, которые мы должны предоставить. Сначала мы явно представляем и создаем экземпляр A
и B
:
∀ (f : ∀ (y : A). B y) (x : A). B x
app A B
Теперь нам нужно объединить то, что мы имеем
∀ (f : ∀ (y : A). B y) (x : A). B x
который так же, как
(∀ (y : A). B y) -> ∀ (x : A). B x
и что app ? ?
получает
∀ (x : A'). B' x
Это приводит к
A' ~ ∀ (y : A). B y
B' ~ λ _. ∀ (x : A). B x -- B' ignores its argument
(см. также Что такое предсказуемость? )
Наше выражение (после некоторого переименования) становится
∀ (A : *) (B : A -> *). ?
λ A B . app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) (app A B)
Так как для любого A
, B
и f
(где f ∈ ∀ (y : A). B y
)
∀ (y : A). B y
app A B f
мы можем создать экземпляр A
и B
получить (для любого f
с соответствующим типом)
∀ (y : ∀ (x : A). B x). ∀ (x : A). B x
app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) f
и сигнатура типа эквивалентна (∀ (x : A). B x) -> ∀ (x : A). B x
.
Все выражение
∀ (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)
Т.е.
∀ (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
который после всех сокращений на уровне значения возвращает то же самое app
.
Таким образом , в то время как он требует всего несколько шагов в чистом лямбда - исчислении , чтобы получить app
от app app
, в типизированной настройки (и особенно в зависимости напечатал) мы также должны заботиться об объединении и вещи становятся все более сложными , даже с некоторым inconsitent удобства ( * ∈ *
).
Проверка типа
- Если
t
есть *
то t ∈ *
(1)
- Если
t
есть ∀ (x : σ) τ
, σ ∈? *
, τ ∈? *
(смотрите примечание о ∈?
ниже) , то в t ∈ *
силу (2)
- Если
t
есть f x
, f ∈ ∀ (v : σ) τ
для некоторых σ
и τ
, x ∈? σ
то t ∈ SUBS(τ, v, x)
(4)
- Если
t
является переменной v
, v
была введена к тому ∀ (v : σ). τ
времени t ∈ σ
(5)
Все это правила вывода, но мы не можем сделать то же самое для лямбд (вывод типов неразрешим для зависимых типов). Поэтому для лямбд мы проверяем ( t ∈? σ
), а не выводим:
- Если
t
есть λ v. b
и проверено ∀ (v : σ) τ
, b ∈? τ
тоt ∈ ∀ (v : σ) τ
- Если
t
это что-то еще и проверено, σ
то определите тип t
использования функции выше и проверьте, является ли этоσ
Проверка на равенство типов требует, чтобы они были в нормальных формах, поэтому, чтобы решить, t
имеет ли тип, σ
мы сначала проверяем, σ
имеет ли тип *
. Если это так, то σ
это нормализуемо (по парадоксу по модулю Жирара), и оно нормализуется (следовательно, σ
становится хорошо сформированным (0)). SUBS
также нормализует выражения для сохранения (0).
Это называется двунаправленной проверкой типов. При этом нам не нужно аннотировать каждую лямбду с типом: если f x
тип f
известен, то x
проверяется на соответствие типу f
получаемого аргумента, а не выводится и сравнивается на равенство (что также менее эффективно). Но если f
это лямбда, это требует явной аннотации типа (аннотации опущены в грамматике, и везде вы можете добавлять Ann Term Term
или λ' (σ : Term) (v : Var)
к конструкторам).
Кроме того, посмотрите на проще, проще! Сообщение блога.