Sto cercando di imparare GHC Generics. Dopo aver esaminato diversi esempi, ho voluto provare a creare istanze generiche Functor
(ignorando che GHC può ricavarle automaticamente per me). Tuttavia, mi sono reso conto che non ho idea di come lavorare con tipi di dati parametrizzati con Generics, tutti gli esempi che ho visto erano di tipo *
. È possibile, e se sì, come? (Sono interessato anche ad altri framework simili, come SYB.)Come costruire istanze di Functor generiche utilizzando GHC.Generics (o altri framework simili)?
9
A
risposta
8
Il posto migliore per cercare molte funzioni di esempio utilizzando GHC Generics è lo generic-deriving
package. C'è una definizione generica della classe Functor
. La copia (leggermente semplificata) da Generics.Deriving.Functor
:
class GFunctor' f where
gmap' :: (a -> b) -> f a -> f b
instance GFunctor' U1 where
gmap' _ U1 = U1
instance GFunctor' Par1 where
gmap' f (Par1 a) = Par1 (f a)
instance GFunctor' (K1 i c) where
gmap' _ (K1 a) = K1 a
instance (GFunctor f) => GFunctor' (Rec1 f) where
gmap' f (Rec1 a) = Rec1 (gmap f a)
instance (GFunctor' f) => GFunctor' (M1 i c f) where
gmap' f (M1 a) = M1 (gmap' f a)
instance (GFunctor' f, GFunctor' g) => GFunctor' (f :+: g) where
gmap' f (L1 a) = L1 (gmap' f a)
gmap' f (R1 a) = R1 (gmap' f a)
instance (GFunctor' f, GFunctor' g) => GFunctor' (f :*: g) where
gmap' f (a :*: b) = gmap' f a :*: gmap' f b
instance (GFunctor f, GFunctor' g) => GFunctor' (f :.: g) where
gmap' f (Comp1 x) = Comp1 (gmap (gmap' f) x)
class GFunctor f where
gmap :: (a -> b) -> f a -> f b
default gmap :: (Generic1 f, GFunctor' (Rep1 f))
=> (a -> b) -> f a -> f b
gmap = gmapdefault
gmapdefault :: (Generic1 f, GFunctor' (Rep1 f))
=> (a -> b) -> f a -> f b
gmapdefault f = to1 . gmap' f . from1
Per utilizzare questo su un tipo di dati, è necessario ricavare Generic1
piuttosto che Generic
. La differenza chiave della rappresentazione Generic1
consiste nel fatto che utilizza il tipo di dati Par1
che codifica le posizioni dei parametri.
3
Esiste una classe Generic1
per tipi di dati di tipo * -> *
. Lavorare con esso è per lo più lo stesso con tipi di dati di tipo *
, ad eccezione del parametro Par1
. L'ho usato nel mio unfoldable package per esempio.
GHC ricava automaticamente istanze di 'Generic1'? –
@ PetrPudlák Non completamente automatico. Ma con l'estensione di linguaggio 'DeriveGeneric', puoi usare' derivando Generic' e 'derivando Generic1' (dove quest'ultimo funziona solo per i tipi di dati con almeno un parametro, l'ultimo parametro è di tipo' * '). – kosmikus
@kosmikus Grazie. Sfortunatamente per il mio obiettivo mi piacerebbe lavorare con tipi più complessi, quindi probabilmente dovrò usare Template Haskell. –