Inferência de tipo com tipos de produto

15

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.

e::=x|ee|[e]|

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 1e1e2e1e2=e2e1

Os termos que não sejam composição têm regras básicas de tipo:

x:ι[Lit]Γe:σΓ[e]:α.ασ×α[Quot],α not free in Γ

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.ab=b×(a)

Existem seis primitivos. Os cinco primeiros são bastante inócuos. duppega o valor mais alto e produz duas cópias. swapaltera a ordem dos dois principais valores. popdescarta o valor superior. quotepega um valor e produz uma cotação (função) que o retorna. applyaplica uma cotação à pilha.

dup::Ab.AbAbbswap::Abc.AbcAcbpop::Ab.AbAquote::Ab.AbA(C.CCb)apply::AB.A(AB)B

O último combinador, composedeve pegar duas aspas e retornar o tipo de concatenação, ou seja, . Na linguagem concatenativa estaticamente tipada Cat , o tipo de é muito direto.[e1][e2]compose=[e1e2]compose

compose::ABCD.A(BC)(CD)A(BD)

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?

compose::ABCDE.A(BC)(DE)A

Se você deixar denotar uma diferença de dois tipos, acho que você pode escrever o tipo corretamente.compose

compose::ABCDE.A(BC)(DE)A((DC)B((CD)E))

Isto ainda é relativamente simples: composeassume 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 2f1:BCf2:DEBf2f1Df1f2

Γe1:AB.ABΓe2:CD.CDΓe1e2:((CB)A((BC)D))[Comp]

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?

A.()A=()A.A()=AABCD.ABCD=BD iff A=Cotherwise=undefined

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.)

Jon Purdy
fonte
Como você usa variáveis ​​em sua gramática? Esta pergunta deve ajudá-lo a lidar com o "subtipo" que você parece precisar.
Jmad
11
@ jmad: Não sei se entendi a pergunta. Variáveis ​​de tipo existem apenas para definir formalmente esquemas de tipos, e a própria linguagem não possui variáveis, apenas definições, que podem ser [mutuamente] recursivas.
precisa
Justo. Você pode dizer por que (talvez com um exemplo) a regra para 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)C=D
Jmad
@jmad: Claro. Considere twicedefinido como dup compose apply, que recebe uma cotação e a aplica duas vezes. [1 +] twiceestá 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] twiceAb.f1,f2:AbAAAbAb.AbbAcompose
21412 Jon Purdy

Respostas:

9

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.

compose:ABCδ.δ (α.α AαB) (β.β BβC)δ (γ.γ AγC)

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):

  • Simon L. Peyton Jones, Dimitrios Vytiniotis, Stephanie Weirich, Mark Shields: Inferência prática de tipo para tipos de classificação arbitrária. J. Funct. Programa. 17 (1): 1-82 (2007)

A regra de tipo para composição é simplesmente:

Γe1:α.α Aα BΓe1:α.α Bα CΓe1 e2:α.α Aα C

Para que o sistema de tipos funcione em geral, você precisa da seguinte regra de especialização:

Γe:α.α Aα BΓe:α.C Aα C B
Dave Clarke
fonte
Obrigado, isso foi muito útil. Esse tipo está correto para funções de um único argumento, mas não suporta vários argumentos. Por exemplo, 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. ιι+ιιι
9114 Jon Purdy
11
Os tipos de pilha quantificam sobre fragmentos de pilha, portanto não há problema em lidar com duas funções de argumento. Não tenho certeza de como isso se aplica dup +, pois isso não usa composição, como você definiu acima.
18712 Dave
Er, certo, eu quis dizer [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). αBB×αB=ι×ι(ι×ι)×αι×(ι×α)
21412 Jon Purdy
Eu posso estar construindo minha pilha na direção errada. Não acho que o aninhamento seja importante, desde que os pares que formam a pilha não apareçam na linguagem de programação. (Eu estou planejando para atualizar a minha resposta, mas necessidade de fazer uma pequena pesquisa em primeiro lugar.)
Dave Clarke
Sim, o aninhamento é praticamente um detalhe de implementação.
21812 Jon Purdy