2013-07-19 22 views
9

Próbuję nauczyć się GHC Generics. Po przejrzeniu kilku przykładów chciałem spróbować stworzyć ogólne instancje Functor (nie biorąc pod uwagę, że GHC może je dla mnie automatycznie wyprowadzić). Jednak zdałem sobie sprawę, nie mam pojęcia, jak pracować z parametryzowanych typów danych z Generics, wszystkie przykłady widziałem były rodzaju *. Czy to możliwe, a jeśli tak, to w jaki sposób? (Interesuję się również innymi podobnymi frameworkami, takimi jak SYB.)Jak skonstruować ogólne instancje Functor za pomocą GHC.Generics (lub innych podobnych frameworków)?

Odpowiedz

8

Najlepszym miejscem do wyszukiwania wielu przykładowych funkcji przy użyciu GHC Generics jest generic-deriving package. Jest tam ogólna definicja klasy Functor. Kopiowanie (nieco uproszczone) od 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 

Aby użyć tego na typ danych, trzeba czerpać Generic1 zamiast Generic. Kluczową różnicą reprezentacji Generic1 jest to, że wykorzystuje typ danych Par1, który koduje pozycje parametrów.

3

Istnieje klasa Generic1 dla typów danych rodzaju * -> *. Praca z nim przebiega w większości tak samo, jak w przypadku typów danych typu *, z wyjątkiem tego, że istnieje parametr Par1. Użyłem go na przykład w moim unfoldable package.

+0

Czy GHC automatycznie wyprowadza wystąpienia "Generic1"? –

+1

@ PetrPudlák Nie w pełni automatycznie. Ale z rozszerzeniem językowym 'DeriveGeneric' możesz używać' czerpania generycznego' oraz 'czerpania Generic1' (gdzie ten ostatni działa tylko dla typów danych z co najmniej jednym parametrem, ostatni parametr jest rodzaju' * '). – kosmikus

+0

@kosmikus Dziękuję. Niestety dla mojego celu chciałbym pracować z bardziej złożonymi gatunkami, więc prawdopodobnie będę musiał użyć szablonu Haskell. –