2013-01-07 12 views
19

I vantaggi dell'utilizzo del tipo sinonimo di famiglie sono chiare: si tratta di funzioni a livello di codice.famiglie di dati casi d'uso

Ma non è il caso delle famiglie dati - quindi la mia domanda è: che cosa sono i casi d'uso per le famiglie di dati? Dove dovrei usarlo?

+0

Invece di puntare semplicemente a un tipo esistente, a volte si desidera anche avere il proprio tipo fresco, avvolgendo uno esistente o solo un normale record con vari campi, ma non solo un sinonimo nudo di uno esistente. Puoi usarli nello stesso modo in cui usi normalmente i tipi dichiarati con "data", tranne per il fatto che questa volta la rappresentazione sarà diversa a seconda dell'argomento del tipo che gli darai. –

risposta

23

Un vantaggio è che le famiglie di dati sono iniettabili, a differenza delle famiglie di tipi.

Se hai

type family TF a 
data family DF a 

poi si sa che DF a ~ DF b implica che a ~ b, mentre con il TF, non è necessario - per qualsiasi a si può essere sicuri che DF a è un tipo completamente nuovo (proprio come [a] è un tipo diverso da [b], a meno che, naturalmente, non sia a ~ b, mentre una famiglia di tipi può mappare più tipi di input sullo stesso tipo esistente.

Un secondo è che le famiglie di dati possono essere parzialmente applicate, come qualsiasi altro costruttore di tipi, mentre le famiglie di tipi non possono.

Questo non è un esempio particolarmente nel mondo reale, ma per esempio, si può fare:

data instance DF Int = DInt Int 
data instance DF String = DString String 

class C t where 
    foo :: t Int -> t String 

instance C DF where -- notice we are using DF without an argument 
        -- notice also that you can write instances for data families at all, 
        -- unlike type families 
    foo (DInt i) = DString (show i) 

In sostanza, DF e DF a sono reali, di prima classe, i tipi legittimi, di per sé, come ogni altra digita la tua dichiarazione con data. TF a è solo un modulo intermedio che valuta un tipo.

Ma suppongo che tutto ciò non sia molto illuminante, o almeno non lo era per me, quando mi chiedevo delle famiglie di dati e leggevo cose simili.

Ecco la regola generale passo. Ogni volta che ti ritrovi a ripetere lo schema di una famiglia di tipi e per ogni tipo di input, dichiari un nuovo tipo data per la famiglia di tipi su cui mappare, è preferibile ritagliare l'intermediario e utilizzare invece una famiglia di dati.

Un esempio del mondo reale dalla libreria vector. vector ha diversi tipi di vettori: vettori in scatola, vettori non in scatola, vettori primitivi, vettori memorizzabili. Per ogni tipo di Vector è presente un tipo corrispondente, mutabile MVector (i normali vettori non sono modificabili). Così si presenta così:

type family Mutable v :: * -> * -> * -- the result type has two type parameters 

module Data.Vector{.Mutable} where 
data Vector a = ... 
data MVector s a = ... 
type instance Mutable Vector = MVector 

module Data.Vector.Storable{.Mutable} where 
data Vector a = ... 
data MVector s a = ... 
type instance Mutable Vector = MVector 

[etc.] 

Ora, invece di quello, avrei preferito:

data family Mutable v :: * -> * -> * 

module Data.Vector{.Mutable} where 
data Vector a = ... 
data instance Mutable Vector s a = ... 
type MVector = Mutable Vector 

module Data.Vector.Storable{.Mutable} where 
data Vector a = ... 
data instance Mutable Vector s a = ... 
type MVector = Mutable Vector 

[etc.] 

che codifica per l'invariante che per ogni tipo di Vector c'è esattamente un Mutable Vector tipo, e che non c'è una corrispondenza uno-a-uno tra di loro. La versione mutevole di un è sempre chiamata Vector: questo è il suo nome e non ha altro. Se si dispone di un Mutable Vector, è possibile ottenere il tipo di immutabile corrispondente Vector, perché è proprio lì come un argomento di tipo. Con type family Mutable, una volta applicato a un argomento, viene valutato un tipo di risultato non specificato (presumibilmente chiamato MVector, ma non si può sapere) e non è possibile eseguire il mapping all'indietro.

Problemi correlati