tag:blogger.com,1999:blog-9757377.post738425611521321301..comments2008-07-08T09:34:48.533+01:00Comments on Wadler's Blog: Data Types a la CartePhilip Wadlerhttp://www.blogger.com/profile/12009347515095774366noreply@blogger.comBlogger4125tag:blogger.com,1999:blog-9757377.post-34970662892701453472008-07-08T09:34:00.000+01:002008-07-08T09:34:00.000+01:00Dear PhilipThere is another solution to the Expres...Dear Philip<BR/><BR/>There is another solution to the Expression Problem which I find easier to use. I'm not sure it's common lore so I've decided to post some sample code. The idea of representing types as products of functions (rather than sums) is due to Bruno Oliveira. More on his paper "Generics as a Library" and on his PhD thesis. The code below is only a taster. Of course, errors or omissions are my own:<BR/><BR/>- GADT as a type class (or encode the type as it's fold):<BR/><BR/> class Exp e where<BR/> lit :: TyRange a => a -> e a<BR/> plus :: e Int -> e Int -> e Int<BR/> and :: e Bool -> e Bool -> e Bool<BR/><BR/>- We cannot construct an ill-typed expression, the Haskell type-checker complains.<BR/><BR/>- |TyRange| is the class of indices:<BR/><BR/>class TyRange a<BR/>instance TyRange Int<BR/>instance TyRange Bool<BR/><BR/>- The behaviour is given by instances:<BR/><BR/>newtype Eval a = Eval {eval :: a}<BR/><BR/>instance Exp Eval where<BR/> lit = Eval<BR/> plus x y = Eval (eval x + eval y)<BR/> and x y = Eval (eval x && eval y)<BR/><BR/>Extension is easy:<BR/><BR/>class Exp e => ExpIf e where<BR/> ifOp :: TyRange a => e Bool -> e a -> e a -> e a<BR/><BR/>instance ExpIf Eval where<BR/> ifOp c t e = Eval (if (eval c) then (eval t) else (eval e))<BR/><BR/>class Exp e => ExpMult e where<BR/> mult :: e Int -> e Int -> e Int<BR/><BR/>instance ExpMult Eval where<BR/> mult x y = Eval (eval x * eval y)<BR/><BR/>- Adding new indices is easy:<BR/><BR/>instance TyRange a => TyRange [a]<BR/>instance TyRange Char<BR/><BR/>class Exp e => ExpConcat e where<BR/> conc :: e [Char] -> e [Char] -> e [Char]<BR/><BR/>instance ExpConcat Eval where<BR/> conc x y = Eval (eval x ++ eval y)<BR/><BR/>- Closing expressions is also easy: wrap around a type and provide new functions:<BR/><BR/>data TyRange a => Wrap a = Wrap (forall e. (Exp e, ExpIf e, ExpMult e,<BR/>ExpConcat e) => e a)<BR/><BR/>> evalExp :: TyRange a => Wrap a -> a<BR/>> evalExp (Wrap x) = eval x<BR/><BR/>Compare:<BR/><BR/>exp1 :: Exp e => e Int<BR/>exp1 = plus (lit 3) (lit 3)<BR/>val1 = eval exp1<BR/><BR/>With:<BR/><BR/>exp1' :: Eval Int<BR/>exp1' = plus (lit 3) (lit 3)<BR/>va1' = eval exp1<BR/><BR/>We are limited here to writing only folds, but we can choose to encode expressions using another encoding. <BR/><BR/>Regards,<BR/>Pablo NogueiraPablohttp://www.blogger.com/profile/07580940279216313107noreply@blogger.comtag:blogger.com,1999:blog-9757377.post-67779758832177877952008-03-03T09:32:00.000Z2008-03-03T09:32:00.000ZOleg Kiselyov sends an alterantive solution, devel...Oleg Kiselyov sends an alterantive solution, developed independently of Ryan Ingram's (posted with permission):<BR/><BR/> I'm including the code that solves the problem as described,<BR/>although not in a nice way. On the bright side, no overlapping<BR/>instances are required. How big the drawback of the solution is<BR/>depends on the exactly problem: is your universe of primitive data<BR/>types closed (or rarely extensible) or not. It is possible to greatly<BR/>improve the solution; for example, our HList paper described how to<BR/>define the total order on types (including type constructors). That<BR/>makes deciding `subjugation' and arithmetical problem... We can also<BR/>use the TypeEq predicates to compare any two types; alas that does<BR/>require overlapping instances. Again, given more details I can improve<BR/>the solution. The present solution, given all its obvious drawbacks,<BR/>has one advantage: simplicity. Hopefully it shows the underlying<BR/>ideas.<BR/><BR/> Cheers,<BR/> Oleg<BR/><BR/>{-# OPTIONS -fglasgow-exts #-}<BR/>{-# OPTIONS -fallow-undecidable-instances #-}<BR/><BR/>module Main where<BR/><BR/>data (f :+: g) e = Inl (f e) | Inr (g e) deriving Show<BR/><BR/>newtype HJust a = HJust a<BR/>data HNothing a = HNothing<BR/><BR/>-- We now define predicate, which, given two types f and g, return<BR/>-- decides if f :<: g holds -- and if so, yields a witness<BR/><BR/>class IsSub f g result | f g -> result where<BR/> ginj :: result (f a -> g a)<BR/><BR/>instance IsSub f f HJust where<BR/> ginj = HJust id<BR/><BR/><BR/>instance (IsSub f g b, IsSub' b f g h result)<BR/> => IsSub f (g :+: h) result where<BR/> ginj = ginj' (ginj :: b (f a -> g a)) <BR/><BR/>class IsSub' b f g h result | b f g h -> result where<BR/> ginj' :: b (f a -> g a) -> result (f a -> (g :+: h) a)<BR/><BR/>instance IsSub' HJust f g h HJust where<BR/> ginj' (HJust tr) = HJust (Inl . tr)<BR/><BR/>instance (IsSub f h b, IsSub'' b f g h result)<BR/> => IsSub' HNothing f g h result where<BR/> ginj' _ = ginj'' (ginj :: b (f a -> h a)) <BR/><BR/>class IsSub'' b f g h result | b f g h -> result where<BR/> ginj'' :: b (f a -> h a) -> result (f a -> (g :+: h) a)<BR/><BR/>instance IsSub'' HJust f g h HJust where<BR/> ginj'' (HJust tr) = HJust (Inr . tr)<BR/><BR/>instance IsSub'' HNothing f g h HNothing where<BR/> ginj'' _ = HNothing<BR/><BR/>{- not ok, but what I want to do<BR/><BR/>instance (Functor f, Functor g, Functor h, (:<:) f g) => (:<:) f (g :+: <BR/>h) where<BR/> inj = Inl . inj<BR/><BR/>instance (Functor f, Functor g, Functor h, (:<:) f h) => (:<:) f (g :+: <BR/>h) where<BR/> inj = Inr . inj<BR/><BR/>-}<BR/><BR/>test1 = let (HJust (tr::[Char]->[Char])) = ginj in tr "a" -- "a"<BR/><BR/>data C a = C a deriving Show<BR/><BR/>-- need to define the disequalities<BR/>-- One may either use Template Haskell for that, or TypeEq from<BR/>-- the HList paper<BR/>instance IsSub [] Maybe HNothing where ginj = HNothing<BR/>instance IsSub Maybe [] HNothing where ginj = HNothing<BR/>instance IsSub C [] HNothing where ginj = HNothing<BR/>instance IsSub [] C HNothing where ginj = HNothing<BR/>instance IsSub C Maybe HNothing where ginj = HNothing<BR/>instance IsSub Maybe C HNothing where ginj = HNothing<BR/><BR/>type Sum1 = ([] :+: Maybe)<BR/>type Sum2 = (Sum1 :+: C)<BR/>type Sum3 = (C :+: Sum1)<BR/><BR/>test2 = let (HJust (tr::[Char]->Sum2 Char)) = ginj in tr "a"<BR/>-- Inl (Inl "a")<BR/>test3 = let (HJust (tr::[Char]->Sum3 Char)) = ginj in tr "a"<BR/>-- Inr (Inl "a")<BR/>test4 = let (HJust (tr::Maybe Char->Sum3 Char)) = ginj in tr (Just 'a')<BR/>-- Inr (Inr (Just 'a'))Philip Wadlerhttp://www.blogger.com/profile/12009347515095774366noreply@blogger.comtag:blogger.com,1999:blog-9757377.post-79936138366589151462008-03-03T09:24:00.000Z2008-03-03T09:24:00.000ZWouter Swierstra writes (posted with permission):R...Wouter Swierstra writes (posted with permission):<BR/><BR/>Regarding the question on your blog about "Data types a la carte": the reason its hard to get a symmetric definition of :<: is that the algorithm Haskell uses to infer which instance definition to use doesn't backtrack - it picks the first instance definition that fits and runs with it. As a result, writing a backtracking search on the type-level becomes a bit tricky. (I just saw an e-mail from Ryan Ingram on haskell-cafe that presents one solution.)<BR/><BR/>There is a real penalty for this asymmetry, by the way. With the asymmetric definition in the paper, you can't group together certain signatures and combine these pieces into even larger signatures. For example:<BR/><BR/>Add :<: (Val :+: Add :+: Sub :+: Mul :+: Div)<BR/><BR/>is fine, but:<BR/><BR/>Add :<: Val :+: (Add :+: Sub) :+: (Mul :+: Div)<BR/><BR/>isn't.Philip Wadlerhttp://www.blogger.com/profile/12009347515095774366noreply@blogger.comtag:blogger.com,1999:blog-9757377.post-76986530675703729832008-02-29T00:57:00.000Z2008-02-29T00:57:00.000ZI've posted a solution to your question hereI've posted a solution to your question <A HREF="http://www.haskell.org/pipermail/haskell-cafe/2008-February/040098.html" REL="nofollow">here</A>Ryan Ingramnoreply@blogger.com