aviso Legal
Isso é muito informal, como você solicitou.
A gramática
Em uma linguagem de tipo dependente, temos um fichário no nível do tipo e no nível do valor:
Term = * | (∀ (Var : Term). Term) | (Term Term) | (λ Var. Term) | Var
Termo bem digitado é um termo com tipo anexado, escreveremos t ∈ σ
ou
σ
t
para indicar que o termo t
tem tipo σ
.
Regras de digitação
Por uma questão de simplicidade, exigimos isso em λ v. t ∈ ∀ (v : σ). τ
ambos λ
e ∀
vinculamos a mesma variável ( v
neste caso).
Regras:
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)
Assim, *
é "o tipo de todos os tipos" (1), ∀
forma tipos a partir de tipos (2), abstrações lambda têm tipos pi (3) e, se v
introduzido por ∀ (v : σ). τ
, então v
possui o tipo σ
(5).
"na forma normal" significa que realizamos o maior número possível de reduções usando a regra de redução:
Regra de redução "A"
(λ 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.
Ou na sintaxe bidimensional em que
σ
t
significa t ∈ σ
:
(∀ (v : σ). τ) σ SUBS(τ, v, t)
~>
(λ v . b) t SUBS(b, v, t)
Só é possível aplicar uma abstração lambda a um termo quando o termo tiver o mesmo tipo que a variável no quantificador associado a todos. Em seguida, reduzimos a abstração lambda e o quantificador forall da mesma maneira que no cálculo lambda puro anterior. Após subtrair a parte do nível de valor, obtemos a regra de digitação (4).
Um exemplo
Aqui está o operador do aplicativo de funções:
∀ (A : *) (B : A -> *) (f : ∀ (y : A). B y) (x : A). B x
λ A B f x . f x
(abreviamos ∀ (x : σ). τ
se σ -> τ
if τ
não menciona x
)
f
retorna B y
para qualquer fornecido y
do tipo A
. Nós aplicamos f
para x
, que é do tipo certo A
, e substituto y
para x
no ∀
depois .
, assim f x ∈ SUBS(B y, y, x)
~> f x ∈ B x
.
Vamos agora abreviar o operador de aplicativo de função como app
e aplicá-lo a si mesmo:
∀ (A : *) (B : A -> *). ?
λ A B . app ? ? (app A B)
Coloco ?
termos que precisamos fornecer. Primeiro, apresentamos e instanciamos explicitamente A
e B
:
∀ (f : ∀ (y : A). B y) (x : A). B x
app A B
Agora precisamos unificar o que temos
∀ (f : ∀ (y : A). B y) (x : A). B x
que é o mesmo que
(∀ (y : A). B y) -> ∀ (x : A). B x
e o que app ? ?
recebe
∀ (x : A'). B' x
Isto resulta em
A' ~ ∀ (y : A). B y
B' ~ λ _. ∀ (x : A). B x -- B' ignores its argument
(veja também O que é predicatividade? )
Nossa expressão (depois de renomear) se torna
∀ (A : *) (B : A -> *). ?
λ A B . app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) (app A B)
Desde para qualquer A
, B
e f
(onde f ∈ ∀ (y : A). B y
)
∀ (y : A). B y
app A B f
podemos instanciar A
e B
obter (para qualquer um f
do tipo apropriado)
∀ (y : ∀ (x : A). B x). ∀ (x : A). B x
app (∀ (x : A). B x) (λ _. ∀ (x : A). B x) f
e a assinatura do tipo é equivalente a (∀ (x : A). B x) -> ∀ (x : A). B x
.
Toda a expressão é
∀ (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)
Ou seja,
∀ (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
que depois de todas as reduções no nível de valor dá o mesmo app
retorno.
Então, enquanto ele requer apenas alguns passos no cálculo lambda pura para obter app
a partir de app app
, em um ambiente digitada (e especialmente um dependente digitado) também precisa se preocupar com a unificação e as coisas se tornam mais complexas, mesmo com alguma conveniência inconsistentes ( * ∈ *
).
Verificação de tipo
- Se
t
for *
então t ∈ *
por (1)
- Se
t
é ∀ (x : σ) τ
, σ ∈? *
, τ ∈? *
(veja a nota sobre ∈?
a seguir), em seguida, t ∈ *
por (2)
- Se
t
for f x
, f ∈ ∀ (v : σ) τ
para alguns σ
e τ
, x ∈? σ
então , t ∈ SUBS(τ, v, x)
por (4)
- Se
t
é uma variável v
, v
foi introduzido até ∀ (v : σ). τ
então t ∈ σ
por (5)
Todas essas são regras de inferência, mas não podemos fazer o mesmo com lambdas (a inferência de tipo é indecidível para tipos dependentes). Portanto, para lambdas, verificamos ( t ∈? σ
) em vez de inferir:
- Se
t
for λ v. b
e verificado contra ∀ (v : σ) τ
, b ∈? τ
entãot ∈ ∀ (v : σ) τ
- Se
t
houver outra coisa com a qual σ
comparar, deduza o tipo de t
uso da função acima e verifique se estáσ
A verificação de igualdade para tipos requer que eles estejam em formas normais, para decidir se t
tem um tipo σ
, primeiro verificamos se σ
há um tipo *
. Nesse caso, σ
é normalizável (paradoxo do módulo Girard) e é normalizado (portanto, σ
torna-se bem formado por (0)). SUBS
também normaliza expressões para preservar (0).
Isso é chamado de verificação de tipo bidirecional. Com ele, não precisamos anotar todos os lambda com um tipo: se f x
o tipo de f
for conhecido, x
será verificado o tipo de argumento f
recebido em vez de inferido e comparado pela igualdade (que também é menos eficiente). Mas se f
for um lambda, ele exige uma anotação de tipo explícita (as anotações são omitidas na gramática e em todo lugar, você pode adicionar Ann Term Term
ou λ' (σ : Term) (v : Var)
aos construtores).
Além disso, dê uma olhada no mais simples, mais fácil! blogpost.