Estou trabalhando em um compilador para uma linguagem concatenativa e gostaria de adicionar suporte à inferência de tipo. Entendo Hindley-Milner, mas tenho aprendido a teoria dos tipos à medida que passo, por isso não tenho certeza de como adaptá-la. O sistema a seguir é sólido e decididamente inferível?
Um termo é um literal, uma composição de termos, uma cotação de um termo ou um primitivo.
Todos os termos denotam funções. Para duas funções e , , isto é, a justaposição indica a composição inversa. Os literais denotam funções niládicas.e 2 e 1
Os termos que não sejam composição têm regras básicas de tipo:
As regras de aplicação são notavelmente ausentes, pois as linguagens concatenativas não possuem.
Um tipo é um literal, uma variável de tipo ou uma função de pilhas para pilhas, em que uma pilha é definida como uma tupla aninhada à direita. Todas as funções são implicitamente polimórficas em relação ao "resto da pilha".
Esta é a primeira coisa que parece suspeita, mas não sei exatamente o que há de errado nisso.
Para facilitar a legibilidade e reduzir os parênteses, presumo que nos esquemas de tipos. Também usarei uma letra maiúscula para uma variável que denota uma pilha, em vez de um único valor.
Existem seis primitivos. Os cinco primeiros são bastante inócuos. dup
pega o valor mais alto e produz duas cópias. swap
altera a ordem dos dois principais valores. pop
descarta o valor superior. quote
pega um valor e produz uma cotação (função) que o retorna. apply
aplica uma cotação à pilha.
O último combinador, compose
deve pegar duas aspas e retornar o tipo de concatenação, ou seja, . Na linguagem concatenativa estaticamente tipada Cat , o tipo de é muito direto.compose
No entanto, esse tipo é muito restritivo: requer que a produção da primeira função corresponda exatamente ao consumo da segunda. Na realidade, você precisa assumir tipos distintos e depois unificá-los. Mas como você escreveria esse tipo?
Se você deixar denotar uma diferença de dois tipos, acho que você pode escrever o tipo corretamente.compose
Isto ainda é relativamente simples: compose
assume uma função e uma . Seu resultado consome acima do consumo de não produzido por e produz acima da produção de não consumido por . Isso dá a regra para a composição comum.f 2 : D → E B f 2 f 1 D f 1 f 2
No entanto, não sei se esse hipotético realmente corresponde a alguma coisa, e eu o persigo em círculos há tempo suficiente para achar que tomei uma decisão errada. Poderia ser uma simples diferença de tuplas?
Existe algo terrivelmente quebrado sobre isso que não estou vendo ou estou no caminho certo? (Provavelmente quantifiquei algumas dessas coisas incorretamente e também apreciaria correções nessa área.)
compose
é muito restritiva? Tenho a impressão de que isso está bem assim. (por exemplo, a restrição pode ser tratado por unificação como para aplicação em como no λ-cálculo)twice
definido comodup compose apply
, que recebe uma cotação e a aplica duas vezes.[1 +] twice
está bom: você está compondo duas funções do tipo . Mas não é: se , o problema é , portanto a expressão é desaprovada, mesmo que deva ser válida e ter digite . É claro que a solução é colocar o qualificador no lugar certo, mas estou me perguntando principalmente como realmente escrever o tipo sem alguma definição circular.[pop] twice
compose
Respostas:
O seguinte rank-2 tipo parece ser suficientemente geral. É muito mais polimórfico do que o tipo proposto na pergunta. Aqui, a variável quantifica sobre pedaços contíguos da pilha, que capturam funções com vários argumentos.
Letras gregas são usadas para as variáveis restantes da pilha apenas para maior clareza.
Ele expressa as restrições de que a pilha de saída do primeiro elemento na pilha precisa ser a mesma que a pilha de entrada do segundo elemento. Instanciar adequadamente a variável para os dois argumentos realmente é a maneira de fazer com que as restrições funcionem corretamente, em vez de definir uma nova operação, como você propõe na pergunta.B
A verificação de tipos do tipo 2 é indecidível em geral, acredito, embora tenha sido realizado algum trabalho que produz bons resultados na prática (para Haskell):
A regra de tipo para composição é simplesmente:
Para que o sistema de tipos funcione em geral, você precisa da seguinte regra de especialização:
fonte
dup +
deve ter o tipo porque tem o tipo . Mas a inferência de tipo na ausência de anotações é um requisito absoluto, portanto, claramente, preciso voltar à prancheta. Tenho uma idéia para outra abordagem a seguir, e vou postar um blog sobre isso se der certo.+
dup +
, pois isso não usa composição, como você definiu acima.[dup] [+] compose
. Mas eu li como ; diga ; então você tem e não . O aninhamento não está correto, a menos que você gire a pilha para que o topo seja o último elemento (aninhado mais profundo).