
Hi, Thanks everyone for their patience on my question and took the time to write back. I was thinking to re-phrase my question (to correct some typo etc), but many of you have already guessed my intent. I am trying to write a simple Matrix library, as my little learning exercise, so TypeConA : Scalar TypeConB : 1D array TypeConC : 2D array /matrix. So I would like to have one typeclass for operations like Scalar +/* Matrix etc. Felipe: you are way ahead of me (like showing me the answer before I do my exam), and I really appreciate your example code, since that is the level of understanding of Haskell I am looking forward to. I don't think I really understand the code yet, but I will give it a try and let you know. As this moment my level of understanding is basic Haskell syntax, basic Monad (going to try Monad/IArray for in-place non-destruction update) and just started to read up on Control.Applicative (and arrows) and Existential types. I have never even heard of phantom types until now. BTW, how do I generate "literate" Haskell code? I keep reading it but I still don't know how to make one (I am assuming it is more complicated then just type the code in with ">" in emacs). //pip On 2009-11-17, at 6:40 AM, Felipe Lessa wrote:
(This e-mail is literate Haskell)
Not that this is the right solution to your problems, but...
{-# LANGUAGE GADTs, EmptyDataDecls, FlexibleInstances, FlexibleContexts #-}
import Control.Applicative
This requires EmptyDataDecls:
data TypeConA data TypeConB data TypeConC
We're gonna use those empty data types as phantom types in our data type below. This requires GADTs:
data TypeCon t a where ValConA :: a -> TypeCon TypeConA a ValConB :: [TypeCon TypeConA a] -> TypeCon TypeConB a ValConC :: [TypeCon TypeConB a] -> TypeCon TypeConC a
Using the phantom types we tell the type system what kind of value we want. Now, some useful instances because we can't derive them:
instance Show a => Show (TypeCon t a) where showsPrec n x = showParen (n > 10) $ case x of ValConA a -> showString "ValConA " . showsPrec 11 a ValConB a -> showString "ValConB " . showsPrec 11 a ValConC a -> showString "ValConC " . showsPrec 11 a
instance Eq a => Eq (TypeCon t a) where (ValConA a) == (ValConA b) = (a == b) (ValConB a) == (ValConB b) = (a == b) (ValConC a) == (ValConC b) = (a == b) _ == _ = error "never here"
The 't' phantom type guarantees that we'll never reach that last definition, e.g.
*Main> (ValConA True) == (ValConB [])
<interactive>:1:19: Couldn't match expected type `TypeConA' against inferred type `TypeConB' Expected type: TypeCon TypeConA Bool Inferred type: TypeCon TypeConB a In the second argument of `(==)', namely `(ValConB [])' In the expression: (ValConA True) == (ValConB [])
instance Functor (TypeCon t) where fmap f (ValConA a) = ValConA (f a) fmap f (ValConB a) = ValConB (fmap (fmap f) a) fmap f (ValConC a) = ValConC (fmap (fmap f) a)
Now, if you want applicative then you'll need FlexibleInstances because we can't write 'pure :: a -> TypeCon t a'; this signature means that the user of the function may choose any 't' he wants, but we can give him only one of the 't's that appear in the constructors above.
instance Applicative (TypeCon TypeConA) where pure x = ValConA x (ValConA f) <*> (ValConA x) = ValConA (f x) _ <*> _ = error "never here"
instance Applicative (TypeCon TypeConB) where pure x = ValConB [pure x] (ValConB fs) <*> (ValConB xs) = ValConB (fmap (<*>) fs <*> xs) _ <*> _ = error "never here"
instance Applicative (TypeCon TypeConC) where pure x = ValConC [pure x] (ValConC fs) <*> (ValConC xs) = ValConC (fmap (<*>) fs <*> xs) _ <*> _ = error "never here"
Now that we have applicative we can also write, using FlexibleContexts,
liftBinOp :: Applicative (TypeCon t) => (a->b->c) -> TypeCon t a -> TypeCon t b -> TypeCon t c liftBinOp = liftA2
We need that 'Applicative' constraint because the type system doesn't know that we have already defined all possible 'Applicative' instances, so we have to live with that :).
And then we can simply write
instance (Applicative (TypeCon t), Num a) => Num (TypeCon t a) where (+) = liftA2 (+) (-) = liftA2 (-) (*) = liftA2 (*) negate = fmap negate abs = fmap abs signum = fmap signum fromInteger = pure . fromInteger
Finally,
*Main> let x1 = ValConB [ValConA 10, ValConA 7] *Main> let x2 = ValConB [ValConA 5, ValConA 13] *Main> x1 * x2 ValConB [ValConA 50,ValConA 130,ValConA 35,ValConA 91]
HTH,
-- Felipe.