[Haskell-cafe] Query regarding Type classes

Stephen Hicks sdh33 at cornell.edu
Mon Oct 13 18:04:07 EDT 2008


(First of all, sorry for the double reply...)

2008/10/13 Arun Suresh <arun.suresh at gmail.com>:
> Now my client want to write another subclass for Drawable...
> He can do that in any other file... package.. whatever...
>
> How would he do that in Haskell ???
> considering he may not modify the source file in while i have defined the
> Drawable ADT..
>
> I know it is possible using Type Classes... Have a Drawable Type Class..
> etc.. etc..
> Is there probably a better way of dooing it ??

That depends on what you want to do.  If all you want to do is allow
the client to pass Drawable's to your API functions, which then only
use the class interface, you can get away without existentials:

> class Drawable d where
>   area :: d -> Int
>   draw :: d -> IO ()

> foo :: Drawable d -> IO ()
> foo d = do putStrLn $ "This shape has area "++show (area d)
>            draw d

On the other hand, if you want to do something like maintain a list of
drawables then you'll need to use existential types (and therefore
include {-# OPTIONS -fglasgow-exts #-} at the top of the source file):

> data Painting = Painting [(forall d. Drawable d => d)]

> addToPainting :: Drawable d => d -> Painting -> Painting
> addToPainting d (Painting ds) = Painting (d:ds)

> emptyPainting :: Painting
> emptyPainting = Painting []

> computeTotalArea :: Painting -> Int
> computeTotalArea (Painting []) = 0
> computeTotalArea (Painting (d:ds)) = area d + computeTotalArea (Painting ds)

> drawPainting :: Painting -> IO ()
> drawPainting (Painting []) = return ()
> drawPainting (Painting (d:ds)) = draw d >> drawPainting (Painting ds)

You can then expose just the Painting type as an abstract type (with
no exposed constructor), and this is similar to making, say,
 Drawable *painting;
Note that in all these examples (including the C++ one), you *only*
get the class interface to work with.  The main difference is that in
C++, it's a bit easier to downcast, whereas in Haskell it's
practically impossible, short of modifying your class interface:

> class Drawable d where
>   toCircle :: d -> Maybe Circle
>   toCircle _ = Nothing -- default implementation, so clients needn't implement

> instance Drawable Circle where
>   toCircle c = Just c

This could be done more generally as well, if you have a class hierarchy,

> class Drawable d => PointyShape d
> data AnyPointy = PointyShape d => AnyPointy d
> class Drawable d where
>   toPointy :: d -> Maybe AnyPointy

and so on.  But beware the difference between the two signatures

 Drawable a => a -> a -> ...
 (Drawable a,Drawable b) => a -> b -> ...

as pointed out in the OOP vs type classes article.  The former needs a
guarantee that the two arguments are the SAME drawable, and once
you're in an existential, you've lost that information (again, short
of doing some hokey stuff with extra class methods, i.e. convert ::
Drawable d' => d -> d' -> Maybe d
but that's just really ugly and you probably don't want to do that...)

Hope this helps,
steve


More information about the Haskell-Cafe mailing list