Polimorfismo di rango superiore rispetto a tipi non scatolati


10

Ho una lingua in cui i tipi sono unboxed per impostazione predefinita, con inferenza di tipo basata su Hindley – Milner. Vorrei aggiungere un polimorfismo di rango più elevato, principalmente per lavorare con tipi esistenziali.

Penso di capire come controllare questi tipi, ma non sono sicuro di cosa fare durante la compilazione. Attualmente, compilo le definizioni polimorfiche generando specializzazioni, proprio come i modelli C ++, in modo che possano lavorare con valori non boxati. Ad esempio, data una definizione di f<T>, se il programma invoca solo f<Int32>e f<Char>, nel programma compilato compaiono solo quelle specializzazioni. (Sto assumendo la compilazione dell'intero programma per ora.)

Ma quando si passa una funzione polimorfica come argomento, non vedo come posso generare staticamente la giusta specializzazione, perché la funzione potrebbe essere selezionata in fase di esecuzione. Non ho altra scelta che usare una rappresentazione in scatola? O c'è un modo per aggirare il problema?

Il mio primo pensiero è stato quello di codificare in qualche modo di ranghi n polimorfismo come livello 1, ma non credo che sia possibile, in generale, perché una formula in logica costruttiva non deve necessariamente avere un prenessa forma normale.


Un'alternativa è ridurre la quantità di boxing necessaria memorizzando bitmap per le quali argomenti di una funzione e parole in memoria sono puntatori. Quindi una funzione / struttura polimorfica è in realtà polimorfica su un puntatore o una parola di dati arbitraria, e le strutture possono memorizzare il loro ultimo campo (anche se è polimorfico) in linea. Queste bitmap possono anche essere utilizzate dal GC per evitare la necessità di parole chiave per tipi non somma.
fread2281,

@ fread2281: in realtà facevo qualcosa del genere in una versione precedente della lingua. Al momento non generi tag per tipi non somma e non esiste un GC. Penso che sia compatibile anche con l'approccio di Neel K.
Jon Purdy,

Risposte:


6

Ci ho pensato un po '. Il problema principale è che in generale non sappiamo quanto sia grande un valore di tipo polimorfico. Se non disponi di queste informazioni, devi ottenerle in qualche modo. La monomorfizzazione ottiene queste informazioni per te specializzando il polimorfismo. La boxe ottiene queste informazioni per te mettendo tutto in una rappresentazione di dimensioni conosciute.

Una terza alternativa è quella di tenere traccia di queste informazioni nei tipi. Fondamentalmente, ciò che puoi fare è introdurre un tipo diverso per ogni dimensione dei dati, e quindi le funzioni polimorfiche possono essere definite su tutti i tipi di una determinata dimensione. Schizzo un tale sistema qui sotto.

tipiκ:: =nDigitare costruttoriUN:: =un':κ.UN|α|UN×B|UN+B|UNB|refUN|Pun'd(K)|μα:κ.UN

Qui, l'idea di alto livello è che il tipo di un tipo ti dice quante parole ci vogliono per disporre un oggetto in memoria. Per ogni data dimensione, è facile essere polimorfico su tutti i tipi di quella particolare dimensione. Poiché ogni tipo, anche quelli polimorfici, ha ancora una dimensione nota, la compilazione non è più difficile di quanto lo sia per C.

Le regole di kinding trasformano questo inglese in matematica e dovrebbero assomigliare a questo:

α:nΓΓα:nΓ,α:nUN:mΓα:n.UN:m
ΓUN:nΓB:mΓUN×B:n+mΓUN:nΓB:nΓUN+B:n+1
ΓUN:mΓB:nΓUNB:1ΓUN:nΓrefUN:1
ΓPun'd(K):KΓ,α:nUN:nΓμα:n.UN:n

Quindi il quantificatore forall richiede che tu dia il tipo che stai superando. Allo stesso modo, l'associazione è un tipo di coppia non in box, che indica semplicemente una accanto a una in memoria (come un tipo di struttura C). I sindacati disgiunti assumono due valori della stessa dimensione, quindi aggiungono una parola per un tag discriminatore. Le funzioni sono chiusure, rappresentate come al solito da un puntatore a un record dell'ambiente e del codice.UN×BUNB

I riferimenti sono interessanti: i puntatori sono sempre una parola, ma possono indicare valori di qualsiasi dimensione. Ciò consente ai programmatori di implementare il polimorfismo su oggetti arbitrari mediante la boxe, ma non richiede loro di farlo. Infine, una volta che sono in gioco dimensioni esplicite, è spesso utile introdurre un tipo di imbottitura, che utilizza lo spazio ma non fa nulla. (Quindi se vuoi prendere l'unione disgiunta di un int e una coppia di ints, dovrai aggiungere il primo int di riempimento, in modo che il layout dell'oggetto sia uniforme.)

I tipi ricorsivi hanno la regola di formazione standard, ma si noti che le occorrenze ricorsive devono avere le stesse dimensioni, il che significa che di solito è necessario inserirle in un puntatore per far funzionare la digitazione. Ad esempio, il tipo di dati elenco potrebbe essere rappresentato come

μα:1.ref(Pun'd(2)+iont×α)

Quindi questo punta a un valore di elenco vuoto o a una coppia di un int e un puntatore a un altro elenco collegato.

Anche la verifica del tipo per sistemi come questo non è molto difficile; l'algoritmo nel mio articolo ICFP con Joshua Dunfield, il controllo dei caratteri bidirezionale completo e facile per il polimorfismo di rango superiore si applica a questo caso quasi senza modifiche.


Bene, penso che questo copra perfettamente il mio caso d'uso. Ero consapevole di usare i generi per ragionare sulle rappresentazioni di valore (come GHC's *vs. #), ma non avevo considerato di farlo in questo modo. Sembra ragionevole limitare quantificatori di livello superiore a tipi di dimensioni conosciute, e penso che questo mi consentirebbe anche di generare staticamente specializzazioni per dimensione, senza bisogno di conoscere il tipo effettivo. Ora è tempo di rileggere quel documento. :)
Jon Purdy,

1

Questo sembra essere più vicino a un problema di compilazione che a un problema di "informatica teorica", quindi probabilmente è meglio chiedere altrove.

Nel caso generale, in effetti, penso che non ci sia altra soluzione che usare una rappresentazione in scatola. Ma mi aspetto anche che in pratica ci siano molte opzioni alternative diverse, a seconda delle specifiche della tua situazione.

Ad esempio, la rappresentazione di basso livello di argomenti non in scatola può di solito essere classificata in pochissime alternative, ad esempio numeri interi o simili, a virgola mobile o puntatore. Quindi, per una funzione f<T>, forse hai solo bisogno di generare 3 diverse implementazioni senza scatola e puoi rappresentare quella polimorfica come una tupla di quelle 3 funzioni, quindi istanziare T su Int32 è solo selezionare il primo elemento della tupla, ...


Grazie per l'aiuto. Non ero davvero sicuro di dove chiedere, dal momento che un compilatore spazia dalla teoria di alto livello fino all'ingegneria di basso livello, ma ho pensato che le persone qui intorno avrebbero qualche idea. Sembra che la boxe possa davvero essere l'approccio più flessibile qui. Dopo aver letto la tua risposta e riflettuto su di essa, l'unica altra ragionevole soluzione che sono stato in grado di trovare è quella di rinunciare a una certa flessibilità e richiedere che gli argomenti polimorfici siano conosciuti staticamente, ad esempio, passandoli come parametri di tipo stessi. Sono compromessi fino in fondo. : P
Jon Purdy,

4
La domanda del PO contiene problemi TCS perfettamente validi, come come fare l'inferenza del tipo quando Damas-Hindley-Milner viene esteso con tipi di rango più elevato. In generale il polimorfismo di rango 2 ha un'inferenza di tipo decidibile ma per inferenza di tipo r> 2 è indecidibile. Non so se la restrizione di Damas-Hindley-Milner cambi questo. Infine, quasi tutto ciò che fanno i compilatori moderni dovrebbe far parte di TCS, ma di solito non lo è perché gli implementatori del compilatore sono in vantaggio rispetto ai teorici.
Martin Berger,
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.