Inferenza del tipo con i tipi di prodotto


15

Sto lavorando a un compilatore per un linguaggio concatenativo e vorrei aggiungere il supporto per l'inferenza del tipo. Capisco Hindley-Milner, ma sto imparando la teoria dei tipi mentre vado, quindi non sono sicuro di come adattarlo. Il seguente sistema è sano e decisamente inferibile?

Un termine è un letterale, una composizione di termini, una citazione di un termine o una primitiva.

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

Tutti i termini indicano funzioni. Per due funzioni e1 ed e2 , e1e2=e2e1 , ovvero la giustapposizione indica la composizione inversa. I letterali indicano funzioni niladiche.

I termini diversi da composizione hanno regole di tipo base:

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

In particolare sono assenti le regole per l'applicazione, poiché mancano le lingue concatenative.

Un tipo è letterale, una variabile di tipo o una funzione da pile a pile, dove una pila è definita come una tupla annidata a destra. Tutte le funzioni sono implicitamente polimorfiche rispetto al "resto della pila".

τ::=ι|α|ρρρ::=()|τ×ρσ::=τ|α.σ

Questa è la prima cosa che sembra sospetta, ma non so esattamente cosa ci sia di sbagliato.

Per aiutare la leggibilità e ridurre le parentesi, suppongo che negli schemi di tipo. Userò anche una lettera maiuscola per una variabile che indica uno stack, anziché un singolo valore.ab=b×(a)

Ci sono sei primitivi. I primi cinque sono piuttosto innocui. dupprende il valore più alto e ne produce due copie. swapcambia l'ordine dei primi due valori. popscarta il valore più alto. quoteaccetta un valore e produce un preventivo (funzione) che lo restituisce. applyapplica una quotazione alla pila.

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

L'ultimo combinatore, composedovrebbe prendere due citazioni e restituire il tipo della loro concatenazione, ovvero . Nel linguaggio concatenativo tipicamente staticoCat, il tipo diè molto semplice.[e1][e2]compose=[e1e2]compose

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

Tuttavia, questo tipo è troppo restrittivo: richiede che la produzione della prima funzione corrisponda esattamente al consumo della seconda. In realtà, devi assumere tipi distinti, quindi unificarli. Ma come scriveresti quel tipo?

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

Se lasci che indichi una differenza di due tipi, allora penso che tu possa scrivere il tipo di correttamente.compose

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

Questo è ancora relativamente semplice: composeprende una funzione e uno f 2 : D E . Il suo risultato consuma B in cima al consumo di f 2 non prodotto da f 1 e produce D in cima alla produzione di f 1 non consumato da f 2 . Questo dà la regola per la composizione ordinaria.f1:BCf2:DEBf2f1Df1f2

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

Tuttavia, non so che questo ipotetico corrisponda effettivamente a qualcosa, e lo sto inseguendo in circoli da abbastanza tempo che penso di aver fatto una svolta sbagliata. Potrebbe essere una semplice differenza di tuple?

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

Is there something horribly broken about this that I’m not seeing, or am I on something like the right track? (I’ve probably quantified some of this stuff wrongly and would appreciate fixes in that area as well.)


How do you use variables in your grammar? This question should help you in handling the "subtyping" you seem to need.
jmad

1
@jmad: I’m not sure I understand the question. Type variables are just there for the sake of formally defining type schemes, and the language itself doesn’t have variables at all, just definitions, which can be [mutually] recursive.
Jon Purdy

Fair enough. Can you say why (perhaps with an example) the rule for compose is too restrictive? I have the impression that this is fine like this. (e.g. the restriction C=D could be handled by unification like for application in like in the λ-calculus)
jmad

@jmad: Sure. Consider twice defined as dup compose apply, which takes a quotation and applies it twice. [1 +] twice is fine: you’re composing two functions of type ιι. But [pop] twice is not: if Ab.f1,f2:AbA, the problem is that AAb, so the expression is disallowed even though it ought to be valid and have type Ab.AbbA. The solution is of course to put the qualifier in the right place, but I’m mainly wondering how to actually write the type of compose without some circular definition.
Jon Purdy

Risposte:


9

The following rank-2 type

compose:ABCδ.δ (α.α AαB) (β.β BβC)δ (γ.γ AγC)
seems to be sufficiently general. It is much more polymorphic than the type proposed in the question. Here variable quantify over contiguous chunks of stack, which captures multi-argument functions.

Greek letters are used for the rest-of-the-stack variables for clarity only.

It expresses the constraints that the output stack of the first element on the stack needs to be the same as the input stack of the second element. Appropriately instantiating the variable B for the two actually arguments is the way of getting the constraints to work properly, rather than defining a new operation, as you propose in the question.

Type checking rank-2 types is undecidable in general, I believe, though some work has been done that gives good results in practice (for Haskell):

  • Simon L. Peyton Jones, Dimitrios Vytiniotis, Stephanie Weirich, Mark Shields: Practical type inference for arbitrary-rank types. J. Funct. Program. 17(1): 1-82 (2007)

The type rule for composition is simply:

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

To get the type system to work in general, you need the following specialisation rule:

Γe:α.α Aα BΓe:α.C Aα C B

Thanks, this was very helpful. This type is correct for functions of a single argument, but it doesn’t support multiple arguments. For instance, dup + should have type ιι because + has type ιιι. But type inference in the absence of annotations is an absolute requirement, so clearly I need to go back to the drawing board. I have an idea for another approach to pursue, though, and will blog about it if it works out.
Jon Purdy

1
The stack types quantify over stack fragments, so there is no problem dealing with two argument functions. I'm not sure how this applies to dup +, as that does not use compose, as you defined it above.
Dave Clarke

Er, right, I meant [dup] [+] compose. But I read αB as B×α; say B=ι×ι; then you have (ι×ι)×α and not ι×(ι×α). The nesting isn’t right, unless you flip the stack around so that the top is the last (deepest nested) element.
Jon Purdy

I may be building my stack in the wrong direction. I don't think the nesting matters, so long as the pairs building up the stack do not appear in the programming language. (I'm planning to update my answer, but need to do a little research first.)
Dave Clarke

Yeah, nesting is pretty much an implementation detail.
Jon Purdy
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.