Então, pensei um pouco mais e fiz alguns progressos. Aqui está uma primeira tentativa de codificar o sistema deliciosamente simples (mas inconsistente) de Martin-Löf Set : Setem um estilo combinatório. Não é uma boa maneira de terminar, mas é o lugar mais fácil para começar. A sintaxe dessa teoria de tipo é apenas lambda-cálculo com anotações de tipo, tipos Pi e um conjunto de universo.
A Teoria do Tipo de Alvo
Para fins de integridade, apresentarei as regras. A validade de contexto apenas diz que você pode construir contextos a partir do vazio, juntando novas variáveis que habitam Sets.
G |- valid G |- S : Set
-------------- ----------------------------- x fresh for G
. |- valid G, x:S |- valid
E agora podemos dizer como sintetizar tipos para termos em qualquer contexto dado, e como alterar o tipo de algo até o comportamento computacional dos termos que ele contém.
G |- valid G |- S : Set G |- T : Pi S \ x:S -> Set
------------------ ---------------------------------------------
G |- Set : Set G |- Pi S T : Set
G |- S : Set G, x:S |- t : T x G |- f : Pi S T G |- s : S
------------------------------------ --------------------------------
G |- \ x:S -> t : Pi S T G |- f s : T s
G |- valid G |- s : S G |- T : Set
-------------- x:S in G ----------------------------- S ={beta} T
G |- x : S G |- s : T
Em uma pequena variação do original, tornei lambda o único operador de ligação, portanto, o segundo argumento de Pi deve ser uma função que calcula a maneira como o tipo de retorno depende da entrada. Por convenção (por exemplo, em Agda, mas infelizmente não em Haskell), o escopo de lambda se estende para a direita tanto quanto possível, então você pode frequentemente deixar as abstrações sem colchetes quando elas são o último argumento de um operador de ordem superior: você pode ver que eu fiz isso com Pi. Seu tipo Agda (x : S) -> Tse torna Pi S \ x:S -> T.
( Digressão . Anotações de tipo em lambda são necessárias se você quiser ser capaz de sintetizar o tipo de abstrações. Se você alternar para verificação de tipo como seu modus operandi, ainda precisará de anotações para verificar um beta-redex como (\ x -> t) s, porque você não tem como para adivinhar os tipos das partes do todo. Aconselho os designers modernos a verificar os tipos e excluir beta-redexes da própria sintaxe.)
( Digressão . Este sistema é inconsistente, pois Set:Setpermite a codificação de uma variedade de "paradoxos mentirosos". Quando Martin-Löf propôs esta teoria, Girard enviou-lhe uma codificação em seu próprio Sistema U inconsistente. O paradoxo subsequente devido a Hurkens é o a construção tóxica mais limpa que conhecemos.)
Sintaxe e normalização do combinador
De qualquer forma, temos dois símbolos extras, Pi e Set, então talvez possamos fazer uma tradução combinatória com S, K e dois símbolos extras: eu escolhi U para o universo e P para o produto.
Agora podemos definir a sintaxe combinatória não tipada (com variáveis livres):
data SKUP = S | K | U | P deriving (Show, Eq)
data Unty a
= C SKUP
| Unty a :. Unty a
| V a
deriving (Functor, Eq)
infixl 4 :.
Observe que incluí os meios para incluir variáveis livres representadas por tipo anesta sintaxe. Além de ser um reflexo de minha parte (toda sintaxe digna desse nome é uma mônada livre com returnvariáveis de incorporação e >>=substituição de desempenho), será útil representar estágios intermediários no processo de conversão de termos com ligação à sua forma combinatória.
Aqui está a normalização:
norm :: Unty a -> Unty a
norm (f :. a) = norm f $. a
norm c = c
($.) :: Unty a -> Unty a -> Unty a -- requires first arg in normal form
C S :. f :. a $. g = f $. g $. (a :. g) -- S f a g = f g (a g) share environment
C K :. a $. g = a -- K a g = a drop environment
n $. g = n :. norm g -- guarantees output in normal form
infixl 4 $.
(Um exercício para o leitor é definir um tipo exatamente para as formas normais e aprimorar os tipos dessas operações.)
Representando a Teoria do Tipo
Agora podemos definir uma sintaxe para nossa teoria de tipos.
data Tm a
= Var a
| Lam (Tm a) (Tm (Su a)) -- Lam is the only place where binding happens
| Tm a :$ Tm a
| Pi (Tm a) (Tm a) -- the second arg of Pi is a function computing a Set
| Set
deriving (Show, Functor)
infixl 4 :$
data Ze
magic :: Ze -> a
magic x = x `seq` error "Tragic!"
data Su a = Ze | Su a deriving (Show, Functor, Eq)
Eu uso uma representação de índice de Bruijn à maneira de Bellegarde e Hook (popularizada por Bird e Paterson). O tipo Su atem mais um elemento que a, e nós o usamos como o tipo de variáveis livres em um fichário, com Zecomo a variável recém-ligada e Su xsendo a representação deslocada da antiga variável livre x.
Traduzindo Termos para Combinadores
E com isso feito, adquirimos a tradução usual, baseada na abstração de colchetes .
tm :: Tm a -> Unty a
tm (Var a) = V a
tm (Lam _ b) = bra (tm b)
tm (f :$ a) = tm f :. tm a
tm (Pi a b) = C P :. tm a :. tm b
tm Set = C U
bra :: Unty (Su a) -> Unty a -- binds a variable, building a function
bra (V Ze) = C S :. C K :. C K -- the variable itself yields the identity
bra (V (Su x)) = C K :. V x -- free variables become constants
bra (C c) = C K :. C c -- combinators become constant
bra (f :. a) = C S :. bra f :. bra a -- S is exactly lifted application
Digitando os Combinadores
A tradução mostra a maneira como usamos os combinadores, o que nos dá uma boa pista sobre quais deveriam ser seus tipos. Ue Psão apenas construtores de conjuntos, então, escrevendo tipos não traduzidos e permitindo "notação Agda" para Pi, devemos ter
U : Set
P : (A : Set) -> (B : (a : A) -> Set) -> Set
O Kcombinador é usado para elevar um valor de algum tipo Aa uma função constante sobre algum outro tipo G.
G : Set A : Set
-------------------------------
K : (a : A) -> (g : G) -> A
O Scombinador é usado para levantar aplicações sobre um tipo, do qual todas as partes podem depender.
G : Set
A : (g : G) -> Set
B : (g : G) -> (a : A g) -> Set
----------------------------------------------------
S : (f : (g : G) -> (a : A g) -> B g a ) ->
(a : (g : G) -> A g ) ->
(g : G) -> B g (a g)
Se você observar o tipo de S, verá que ele afirma exatamente a regra de aplicação contextualizada da teoria de tipo, de modo que é o que o torna adequado para refletir a construção do aplicativo. Esse é o seu trabalho!
Então, temos aplicação apenas para coisas fechadas
f : Pi A B
a : A
--------------
f a : B a
Mas há um obstáculo. Eu escrevi os tipos de combinadores na teoria dos tipos comuns, não na teoria dos tipos combinatórios. Felizmente, tenho uma máquina que fará a tradução.
Um sistema de tipo combinatório
---------
U : U
---------------------------------------------------------
P : PU(S(S(KP)(S(S(KP)(SKK))(S(KK)(KU))))(S(KK)(KU)))
G : U
A : U
-----------------------------------------
K : P[A](S(S(KP)(K[G]))(S(KK)(K[A])))
G : U
A : P[G](KU)
B : P[G](S(S(KP)(S(K[A])(SKK)))(S(KK)(KU)))
--------------------------------------------------------------------------------------
S : P(P[G](S(S(KP)(S(K[A])(SKK)))(S(S(KS)(S(S(KS)(S(KK)(K[B])))(S(KK)(SKK))))
(S(S(KS)(KK))(KK)))))(S(S(KP)(S(S(KP)(K[G]))(S(S(KS)(S(KK)(K[A])))
(S(S(KS)(KK))(KK)))))(S(S(KS)(S(S(KS)(S(KK)(KP)))(S(KK)(K[G]))))
(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(S(KS)(S(S(KS)(S(KK)(KS)))
(S(S(KS)(S(KK)(KK)))(S(KK)(K[B])))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(KK)(KK))))
(S(KK)(KK))))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(S(KS)(S(KK)(KK)))
(S(S(KS)(KK))(KK)))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(KK)(KK))))(S(KK)(KK)))))))
M : A B : U
----------------- A ={norm} B
M : B
Então aí está, em toda a sua glória ilegível: uma apresentação combinatória de Set:Set!
Ainda há um pequeno problema. A sintaxe do sistema não oferece nenhuma maneira de adivinhar os parâmetros G, Ae Bpara Se de forma semelhante K, apenas a partir dos termos. Da mesma forma, podemos verificar as derivações de tipagem algoritmicamente, mas não podemos simplesmente verificar os termos do combinador como poderíamos com o sistema original. O que pode funcionar é exigir que a entrada para o verificador de tipo tenha anotações de tipo sobre os usos de S e K, registrando efetivamente a derivação. Mas essa é outra lata de vermes ...
Este é um bom lugar para parar, se você estiver interessado em começar. O resto é coisa de "bastidores".
Gerando os Tipos de Combinadores
Eu gerei esses tipos combinatórios usando a tradução de abstração de colchetes dos termos relevantes da teoria de tipo. Para mostrar como fiz isso, e tornar este post não totalmente inútil, deixe-me oferecer meu equipamento.
Posso escrever os tipos de combinadores, totalmente abstraídos de seus parâmetros, como segue. Eu uso minha pilfunção prática , que combina Pi e lambda para evitar a repetição do tipo de domínio e, de forma bastante útil, me permite usar o espaço de funções de Haskell para vincular variáveis. Talvez você quase possa ler o seguinte!
pTy :: Tm a
pTy = fmap magic $
pil Set $ \ _A -> pil (pil _A $ \ _ -> Set) $ \ _B -> Set
kTy :: Tm a
kTy = fmap magic $
pil Set $ \ _G -> pil Set $ \ _A -> pil _A $ \ a -> pil _G $ \ g -> _A
sTy :: Tm a
sTy = fmap magic $
pil Set $ \ _G ->
pil (pil _G $ \ g -> Set) $ \ _A ->
pil (pil _G $ \ g -> pil (_A :$ g) $ \ _ -> Set) $ \ _B ->
pil (pil _G $ \ g -> pil (_A :$ g) $ \ a -> _B :$ g :$ a) $ \ f ->
pil (pil _G $ \ g -> _A :$ g) $ \ a ->
pil _G $ \ g -> _B :$ g :$ (a :$ g)
Com eles definidos, extraí os subtermos abertos relevantes e os executei na tradução.
A de Bruijn Encoding Toolkit
Veja como construir pil. Em primeiro lugar, defino uma classe de Finconjuntos de itens, usados para variáveis. Cada um desses conjuntos tem um embedding que preserva o construtor no conjunto acima, além de um novo topelemento, e você pode diferenciá-los: a embdfunção informa se um valor está na imagem de emb.
class Fin x where
top :: Su x
emb :: x -> Su x
embd :: Su x -> Maybe x
Podemos, é claro, instanciar Finpara ZeeSuc
instance Fin Ze where
top = Ze -- Ze is the only, so the highest
emb = magic
embd _ = Nothing -- there was nothing to embed
instance Fin x => Fin (Su x) where
top = Su top -- the highest is one higher
emb Ze = Ze -- emb preserves Ze
emb (Su x) = Su (emb x) -- and Su
embd Ze = Just Ze -- Ze is definitely embedded
embd (Su x) = fmap Su (embd x) -- otherwise, wait and see
Agora posso definir menos ou igual, com uma operação de enfraquecimento .
class (Fin x, Fin y) => Le x y where
wk :: x -> y
A wkfunção deve incorporar os elementos de xcomo os maiores elementos de y, de modo que as coisas extras em ysejam menores e, portanto, em termos de índice de de Bruijn, limitados mais localmente.
instance Fin y => Le Ze y where
wk = magic -- nothing to embed
instance Le x y => Le (Su x) (Su y) where
wk x = case embd x of
Nothing -> top -- top maps to top
Just y -> emb (wk y) -- embedded gets weakened and embedded
E uma vez que você tenha resolvido isso, um pouco de malandragem faz o resto.
lam :: forall x. Tm x -> ((forall y. Le (Su x) y => Tm y) -> Tm (Su x)) -> Tm x
lam s f = Lam s (f (Var (wk (Ze :: Su x))))
pil :: forall x. Tm x -> ((forall y . Le (Su x) y => Tm y) -> Tm (Su x)) -> Tm x
pil s f = Pi s (lam s f)
A função de ordem superior não fornece apenas um termo que representa a variável, mas também uma coisa sobrecarregada que se torna a representação correta da variável em qualquer escopo onde a variável esteja visível. Ou seja, o fato de que me dou ao trabalho de distinguir os diferentes escopos por tipo dá ao inspetor de tipos Haskell informações suficientes para calcular o deslocamento necessário para a tradução para a representação de Bruijn. Por que ter um cachorro e latir sozinho?