2015-11-17 23 views
7

mi chiedo perché i tipi disimballati a Haskell hanno queste restrizioni:restrizioni di tipo unboxed

  1. Non è possibile definire un newtype per il tipo unboxed:

    newtype Vec = Vec (# Float#, Float# #) 
    

    ma è possibile definire il tipo sinonimo:

    type Vec = (# Float#, Float# #) 
    
  2. Le famiglie di tipi non possono restituire un tipo non registrato:

    type family Unbox (a :: *) :: # where 
        Unbox Int = Int# 
        Unbox Word = Word# 
        Unbox Float = Float# 
        Unbox Double = Double# 
        Unbox Char = Char# 
    

Ci sono alcune ragioni fondamentali alla base di questo, o è solo perché nessuno ha chiesto per questo dispone?

risposta

7

Il polimorfismo parametrico in Haskell si basa sul fatto che tutti i valori dei tipi t :: * sono rappresentati in modo uniforme come puntatore a un oggetto runtime. Pertanto, lo stesso codice macchina funziona per tutte le istanze di valori polimorfici.

Funzioni polimorfiche di contrasto in Rust o C++. Ad esempio, la funzione di identità ha ancora tipo analogico a forall a. a -> a, ma poiché i valori di diversi tipi a possono avere dimensioni diverse, i compilatori devono generare codice diverso per ogni instatiazione. Ciò significa anche che non possiamo passare funzioni polimorfiche giro in scatole di esecuzione:

data Id = Id (forall a. a -> a) 

poiché tale funzione dovrebbe funzionare correttamente per oggetti arbitrari dimensioni. Richiede qualche infrastruttura aggiuntiva per consentire questa funzionalità, ad esempio potremmo richiedere che una funzione runtime forall a. a -> a prenda argomenti impliciti aggiuntivi che portano informazioni sulla dimensione e sui costruttori/distruttori dei valori a.

Ora, il problema con newtype Vec = Vec (# Float#, Float# #) è che, anche se ha Vec tipo *, codice runtime che prevede valori di alcune t :: * non in grado di gestirlo. È una coppia di float allocata allo stack, non un puntatore a un oggetto Haskell, e passarla al codice che si aspetta oggetti Haskell si tradurrebbe in segfault o errori.

In generale (# a, b #) non è necessariamente a dimensione di puntatore, quindi non è possibile copiarlo in campi di dati di dimensioni puntatore.

Le famiglie di tipi che restituiscono tipi # non sono consentite per motivi correlati. Si consideri il seguente:

type family Foo (a :: *) :: # where 
    Foo Int = Int# 
    Foo a = (# Int#, Int# #) 

data Box = forall (a :: *). Box (Foo a) 

nostro Box non è rappresentabile runtime, poiché Foo a ha dimensioni diverse per i diversi a -s. Generalmente, il polimorfismo su # richiederebbe la generazione di codice diverso per diverse istanze, come in Rust, ma questo interagisce male con il normale polimorfismo parametrico e rende difficile la rappresentazione runtime di valori polimorfici, quindi GHC non si preoccupa di nulla di tutto questo.

(Non dire però che un'implementazione utilizzabile non poteva forse essere concepito)

+1

"problema con' newtype Vec = Vec (# Float #, Float # #) 'è che anche se' Vec' ha un carattere '*' "Ma perché? Perché deve essere gentile '*'? Quando scrivo 'type Vec = (# Float #, Float # #)' Vec ha gentile '#', quindi mi aspetto che newtype abbia anche il carattere '#. –

+1

'type Vec = ...' è solo un sinonimo. 'newtype' definisce, beh, un nuovo tipo che è diverso dal tipo del valore spostato. Succede così che tutti i costrutti Haskell per la definizione di nuovi tipi restituiscono in kind '*'. 'newtype'-s per i tipi unboxed richiederebbe una grossa fetta dell'infrastruttura che ho menzionato sopra per abilitare il polimorfismo su oggetti di dimensioni diverse, altrimenti sarebbe utilizzabile solo in modo monomorfico, il che non li renderebbe particolarmente migliori rispetto al solo uso del tipo avvolto . –

+0

@ AndrásKovács Potrebbe essere di tipo '#', ma ad es. 'id :: a -> a' non può essere specializzato per' a ~ Vec' da 'a :: *'. Questa limitazione è legata al fatto che il runtime gestisce i dati in scatola in modo uniforme (con puntatori) quando si desidera passare dati unboxed non elaborati, quindi è necessario un ID 'personalizzato 'per ogni tipo non inserito. Non molto diverso dall'incapacità di Java di avere generici come 'T ' dato che 'int' non è un tipo di riferimento. – chi

4

Un newtype permetterebbe uno per definire istanze di classe

instance C Vec where ... 

che non possono essere definite per tuple senza custodia. Digitare sinonimi invece non offre tale funzionalità.

Inoltre, Vec non è un tipo in scatola. Ciò significa che non puoi più istanziare variabili di tipo con Vec in generale, a meno che il loro tipo lo consenta. Ad esempio, [Vec] non dovrebbe essere consentito. Il compilatore dovrebbe tenere traccia dei newtypes "regolari" e dei newtypes "unboxed" in qualche modo. Questo, a mio avviso, avrà l'unico vantaggio di consentire al costruttore di dati Vec di racchiudere i valori unboxed in fase di compilazione (poiché viene rimosso in fase di runtime). Probabilmente questo non sarebbe abbastanza utile per giustificare le modifiche necessarie al motore dell'inferenza di tipo, suppongo.

+1

Oh, è dovuto essere la mia terza domanda: perché non si può definire la classe oltre i tipi disimballati in questo modo: 'Classe C (u :: #) dove ... '? –

+0

"Vec non sarebbe un tipo in scatola" Perché deve essere così? Digitare synonim 'type Vec = (# Float #, Float # #)' ha gentile '#' perché 'newtype' no? –

+0

"Il compilatore dovrebbe tenere traccia dei newtype" regolari "e dei newtypes" unboxed "in qualche modo." Il regolare controllo dei tipi non è abbastanza? –