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.
fonte
Respostas:
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.
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.
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
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.
fonte
*
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. :)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, ...fonte