2013-06-17 14 views
7

Dzisiaj chciałem zbadać, czy możliwe jest skonstruowanie typu danych w taki sposób, aby nie przechowywać danych typu typu jego sygnatury, ale inną reprezentację tego typu . Oto moja próba GADT, który ma typ konstruktora typu a, ale konstruktora danych typu ByteString.Instancja Functor dla GADT z ograniczeniem typu

{-# LANGUAGE GADTs #-} 
import Data.ByteString.Char8 
import Data.Serialize 

data Serialized a where 
    MkSerialized :: (Serialize a) => ByteString -> Serialized a 

Teraz mogę zdefiniować decode' funkcję w następujący sposób:

decode' :: (Serialize a) => Serialized a -> a 
decode' (MkSerialized bs) = let Right r = (decode bs) in r 

I to działa:

let s = MkSerialized (encode "test") :: Serialized String 
print $ decode' s  -- prints "test" 

Mój problem jest teraz, że chciałbym być Serialized wystąpienie Functor.

instance Functor Serialized where 
    fmap f (MkSerialized bs) = MkSerialized (encode (f (right (decode bs)))) 
           where right (Right r) = r 

Ale pojawia się błąd (Serialize b) nie można wywnioskować. W jaki sposób można ograniczyć instancję Functor, aby Serialize została wymuszona w ?

+2

Nie możesz. 'Funktor' nie zezwala na wymagania dotyczące parametrów typu. W pakiecie 'rmonad' jest klasa ograniczonego funktora, [' RFunctor'] (http://hackage.haskell.org/packages/archive/rmonad/0.8/doc/html/Control-RMonad.html#t:RFunctor) . Może możesz tego użyć. –

+2

Nie jest to związane z twoim pytaniem - nie jest to możliwe z 'Functor' - ale czuję się zobowiązany wspomnieć: nie używaj domyślnie' Data.ByteString.Char8'! To uszkodzony moduł, który zachęca do zepsutego kodu. Czasami są na to sposoby, ale twój kod działa równie dobrze z 'Data.ByteString', co nie zachęca do nieporozumień w Unicode. – shachaf

+1

Dla tego, co jest warte, można utworzyć typ danych typu "CoYoneda", np. 'Dane Serializowane a gdzie MkSerialized :: Serialize x => ByteString -> (x -> a) -> Serializowane a', które przechowuje ByteString i funkcja po deserializacji, która ma instancję 'Functor'. Ale oczywiście to pokonuje tutaj cel. – shachaf

Odpowiedz

6

Możesz to zrobić, używając CoYoneda functor.

Pomysł jest prosty: posiadać dodatkowe pole funkcjonalne, w którym można gromadzić swoje funkcje . Kiedy dekodujesz swoją wartość, zastosuj tę funkcję.

Oto kod:

{-# LANGUAGE GADTs #-} 
import Data.ByteString.Char8 
import Data.Serialize 

data Serialized a where 
    MkSerialized 
     :: (Serialize a) 
     => ByteString -> (a -> b) -> Serialized b 

decode' :: Serialized a -> a 
decode' (MkSerialized bs f) = let Right r = decode bs in f r 

instance Functor Serialized where 
    fmap f (MkSerialized bs g) = MkSerialized bs (f . g) 

Ma to również korzyści automatycznie fuzję stwardnienie fmap S zamiast powtarzających dekodowania i kodowania, jak będzie w Twoim przypadku.

+2

Chociaż to naprawdę nie rozwiązuje mojego problemu (jak bym chciał, żeby 'fmap' robił wielokrotne de/kodowania), przyjmuję tę odpowiedź b/c Widzę, że mój oryginalny pomysł nie jest możliwy i to jest najbardziej praktyczny sposób zdefiniowania funktora dla ograniczonego GADT. Ponadto [interesująca lektura dla funktorów GADT i Yoneda] (http://www.haskellforall.com/2012/06/gadts.html). – Phae7rae

Powiązane problemy