2013-04-25 25 views
6

Załóżmy, że mamy następującą definicję HList:wspólna ograniczeniem dla pozycji o HList

data HL spec where 
    HLNil :: HL() 
    HLCons :: h -> HL t -> HL (h, t) 

to możliwe, aby w jakiś sposób wymusić ograniczenie udostępnionego na jego pozycji?

Jako przykład Oto moja próba ograniczyć elementy mają Show instancji, który nie działa z Couldn't match type `Char' with `Int':

class HLSpecEach spec item 
instance HLSpecEach() item 
instance (HLSpecEach t item, h ~ item) => HLSpecEach (h, t) item 

a :: (Show item, HLSpecEach spec item) => HL spec -> Int 
a = undefined 

b :: HL (Int, (Char,())) 
b = undefined 

c = a b 

Odpowiedz

4

Łatwe do zrobienia, jeśli masz przymusu rodzajów i rodzin typu. Po pierwsze, chciałbym powiedzieć, że wolą używać DataKinds dla jasności

data HList ls where 
    HNil :: HList '[] 
    HCons :: x -> HList xs -> HList (x ': xs) 

type family ConstrainAll (c :: * -> Constraint) (ls :: [*]) :: Constraint 
type instance ConstrainAll c '[] =() 
type instance ConstrainAll c (x ': xs) = (c x, ConstrainAll c xs) 

showAll :: ConstrainAll Show xs => HList xs -> [String] 
showAll HNil = [] 
showAll (HCons x xs) = (show x) : showAll xs 

jeśli nie korzystać z nowych rozszerzeń to jest możliwe, ale o wiele brzydsze. Jedną opcją jest zdefiniowanie niestandardowych klas dla wszystkich, które uważam za brzydkie. Bardziej sprytny podejście byłoby fałszywe rodzaju przymusu

class Constrained tag aType where 
    isConstained :: tag aType 

data HListT tag ls where 
    HNilT :: HListT tag() 
    HConsT :: x -> tag x -> HListT tag xs -> HListT tag (x,xs) 

data Proxy (f :: * -> *) = Proxy 
class ConstainedAll tag ls where 
    tagThem :: Proxy tag -> HList ls -> HListT tag ls 
instance ConstainedAll tag() where 
    tagThem _ _ = HNilT 
instance (ConstainedAll tag xs, Constrained tag x) => ConstainedAll tag (x,xs) where 
    tagThem p (HCons x xs) = HConsT x isConstained (tagThem p xs) 

które można następnie wykorzystać jak

data Showable x where Showable :: Show x => Showable x 
instance Show x => Constrained Showable x where isConstained = Showable 

--inferred type showAll' :: HListT Showable xs -> [String] 
showAll' HNilT = [] 
showAll' (HConsT x Showable xs) = (show x) : showAll' xs 

--inferred type: showAll :: ConstainedAll Showable xs => HList xs -> [String] 
showAll xs = showAll' (tagThem (Proxy :: Proxy Showable) xs) 

example = showAll (HCons "hello" (HCons() HNil)) 

które powinny (miałaś testowane) pracować z dowolnym GHC z GADTs, MptC, elastyczne Konteksty/przypadkach i Kind Signatures (możesz łatwo pozbyć się ostatniego).

EDIT: W GHC 7.6+ należy użyć

type family ConstrainAll (c :: k -> Constraint) (ls :: [k]) :: Constraint 

(k zamiast *) i włącz PolyKinds, ale to nie będzie działać z realizacją GHC 7.4 PolyKinds (stąd kod jednokształtny). W ten sam sposób definiowania

data HList f ls where 
    HNil :: HList f '[] 
    HCons :: !(f x) -> !(HList f xs) -> HList f (x ': xs) 

pozwala uniknąć powielania kodu kiedy chcesz rzeczy jak leniwy vs ścisłych HLists lub gdy chcesz listę słowników, lub uniwersalne warianty wyższych typów kinded itd

+1

Możesz dodać, że 'Ograniczenie' jest częścią pakietu' constraints', i musisz zaimportować 'Data.Constraint' dla rozwiązania' DataKinds'. Potrzebujesz również rozszerzenia 'ConstraintKinds'. :-) –

+0

Łatwo ... Tak, to nie tak, że może być trudniejsze niż łączenie łańcuchów lub coś takiego, prawda? To łatwe. Easy to nazwa tego! Pewnie! To znaczy, patrzysz na to i całkowicie rozumiesz, dlaczego. Bułka z masłem!) Poważnie, dziękuję! –

+1

Aby być uczciwym, rozwiązanie "Więzy" jest dość łatwe (w porównaniu do innych. Wszystko to dzięki temu, że DataKinds jest naprawdę użyteczny). Możesz zobaczyć bardziej ogólne podejście w tej odpowiedzi SO: http://stackoverflow.com/a/ 12995367/11797 Cały wątek jest całkiem interesujący i myślę, że może ci pomóc. –