Polimorfismo de classificação mais alta sobre tipos sem caixa

10

Eu tenho um idioma no qual os tipos são desmarcados por padrão, com inferência de tipo baseada em Hindley-Milner. Eu gostaria de adicionar um polimorfismo de classificação mais alta, principalmente para trabalhar com tipos existenciais.

Acho que entendo como verificar esses tipos, mas não sei o que fazer ao compilar. Atualmente, eu compilo definições polimórficas gerando especializações, como os modelos C ++, para que eles possam trabalhar com valores sem caixa. Por exemplo, dada uma definição de f<T>, se o programa chama apenas f<Int32>e f<Char>, somente essas especializações aparecem no programa compilado. (Estou assumindo a compilação de todo o programa por enquanto.)

Mas, ao passar uma função polimórfica como argumento, não vejo como posso gerar estaticamente a especialização correta, porque a função pode ser selecionada em tempo de execução. Não tenho escolha a não ser usar uma representação em caixa? Ou existe uma maneira de contornar o problema?

Meu primeiro pensamento foi de alguma forma codificar o polimorfismo de classificação n como classificação 1, mas não acredito que seja possível em geral porque uma fórmula na lógica construtiva não necessariamente tem uma forma normal de prenex.

Jon Purdy
fonte
Uma alternativa é reduzir a quantidade de boxe necessária armazenando bitmaps para os quais argumentos de uma função e palavras na memória são ponteiros. Então, uma função / estrutura polimórfica é na verdade polimórfica sobre um ponteiro ou uma palavra arbitrária de dados, e as estruturas podem armazenar seu último campo (mesmo que seja polimórfico) em linha. Esses bitmaps também podem ser usados ​​pelo GC para evitar a necessidade de palavras-chave para tipos não somados.
Fread2281
@ fread2281: Na verdade, eu costumava fazer algo assim em uma versão mais antiga do idioma. No momento, não gero tags para tipos que não são de soma e não há GC. Eu acho que isso é compatível com a abordagem de Neel K também.
22417 Jon Purdy

Respostas:

6

Eu pensei um pouco sobre isso. A questão principal é que, em geral, não sabemos quão grande é o valor do tipo polimórfico. Se você não possui essas informações, precisa obtê-las de alguma forma. A monomorfização obtém essas informações para você especializando o polimorfismo. O boxe obtém essas informações para você, colocando tudo em uma representação de tamanho conhecido.

Uma terceira alternativa é acompanhar essas informações nos tipos. Basicamente, o que você pode fazer é introduzir um tipo diferente para cada tamanho de dados e, em seguida, funções polimórficas podem ser definidas sobre todos os tipos de um tamanho específico. Vou esboçar esse sistema abaixo.

Kindsκ::=nType constructorsA::=a:κ.A|α|A×B|A+B|AB|refA|Pad(k)|μα:κ.A

Aqui, a ideia de alto nível é que o tipo de tipo diga quantas palavras são necessárias para colocar um objeto na memória. Para qualquer tamanho, é fácil ser polimórfico em todos os tipos desse tamanho específico. Como todo tipo - mesmo os polimórficos - ainda tem um tamanho conhecido, a compilação não é mais difícil do que para C.

α:nΓΓα:nΓ,α:nA:mΓα:n.A:m
ΓA:nΓB:mΓA×B:n+mΓA:nΓB:nΓA+B:n+1
ΓA:mΓB:nΓAB:1ΓA:nΓrefA:1
ΓPumad(k):kΓ,α:nUMA:nΓμα:n.UMA:n

UMA×BUMAB

As referências são interessantes - ponteiros são sempre uma palavra, mas podem apontar para valores de qualquer tamanho. Isso permite que os programadores implementem o polimorfismo em objetos arbitrários por meio de boxe, mas não exige que eles o façam. Finalmente, quando os tamanhos explícitos estão em jogo, geralmente é útil introduzir um tipo de preenchimento, que usa espaço, mas não faz nada. (Portanto, se você deseja obter a união disjunta de um int e um par de entradas, precisará adicionar preenchimento ao primeiro int, para que o layout do objeto seja uniforme.)

Os tipos recursivos têm a regra de formação padrão, mas observe que as ocorrências recursivas devem ter o mesmo tamanho, o que significa que você geralmente precisa colocá-las em um ponteiro para que a classificação funcione. Por exemplo, o tipo de dados da lista pode ser representado como

μα:1ref(Pumad(2)+Eunt×α)

Portanto, isso aponta para um valor de lista vazio ou um par de um int e um ponteiro para outra lista vinculada.

A verificação de tipo para sistemas como esse também não é muito difícil; o algoritmo no meu artigo da ICFP com Joshua Dunfield, Tipechecking bidirecional completo e fácil para polimorfismo de classificação mais alta se aplica a esse caso quase sem alterações.

Neel Krishnaswami
fonte
Legal, acho que isso cobre perfeitamente o meu caso de uso. Eu estava ciente de usar tipos para raciocinar sobre representações de valor (como GHC *vs. #), mas não havia pensado em fazê-lo dessa maneira. Parece razoável restringir quantificadores de classificação mais alta a tipos de tamanho conhecido, e acho que isso também me permitiria gerar estaticamente especializações por tamanho, sem precisar conhecer o tipo real. Agora, é hora de reler esse artigo. :)
Jon Purdy
1

Isso parece estar mais próximo de um problema de compilação do que de "ciência da computação teórica", então é melhor você perguntar em outro lugar.

No caso geral, de fato, acho que não há outra solução senão usar uma representação em caixa. Mas também espero que, na prática, haja muitas opções alternativas diferentes, dependendo das especificidades da sua situação.

Por exemplo, a representação de baixo nível de argumentos sem caixa geralmente pode ser categorizada em muito poucas alternativas, por exemplo, número inteiro ou similar, ponto flutuante ou ponteiro. Portanto, para uma função f<T>, talvez você realmente precise apenas gerar 3 implementações diferentes da caixa do correio e pode representar a polimórfica como uma tupla dessas 3 funções, para instanciar o T para Int32 basta selecionar o primeiro elemento da tupla, ...

Stefan
fonte
Obrigado pela ajuda. Eu não tinha muita certeza de onde perguntar, já que um compilador abrange a teoria de alto nível e a engenharia de baixo nível, mas imaginei que as pessoas por aqui teriam algumas idéias. Parece que o boxe pode realmente ser a abordagem mais flexível aqui. Depois de ler sua resposta e pensar mais sobre ela, a única outra solução razoável que consegui é desistir de alguma flexibilidade e exigir que os argumentos polimórficos sejam conhecidos estaticamente, por exemplo, passando-os como parâmetros de tipo. São trocas por todo o caminho. : P
Jon Purdy
4
A pergunta do OP contém problemas de TCS perfeitamente válidos, como como fazer inferência de tipo quando Damas-Hindley-Milner é estendido com tipos de classificação mais alta. Em geral, o polimorfismo de classificação 2 tem inferência de tipo decidível, mas para inferência de tipo classificação k> 2 é indecidível. Se a restrição de Damas-Hindley-Milner muda isso, eu não sei. Finalmente, praticamente tudo o que os compiladores modernos fazem deve fazer parte do TCS, mas geralmente não é porque os implementadores do compilador estão à frente dos teóricos.
Martin Berger