haskell-cafe@haskell.org
[Top] [All Lists]

Re: [Haskell-cafe] Polymorphic (typeclass) values in a list?

Subject: Re: [Haskell-cafe] Polymorphic (typeclass) values in a list?
From: Jules Bean
Date: Fri, 19 Oct 2007 17:32:10 +0100
TJ wrote:
Why is it illegal to store values of differing types, but which
instance the same class, into a list? e.g.

a = [ 1, 2.0 ] :: Num a => [a]

That type signature doesn't mean what you want it to mean. That reads

"A list of things of type a ([a]) with the restriction that the type a is a member of the Num class"

After all, sometimes all you need to know about a list is that all the
elements support a common set of operations. If I'm implementing a 3d
renderer for example, I'd like to have

That's a reasonable thing to want.


class Renderable a where
  render :: a -> RasterImage

scene :: Renderable a => [a]


Instead of hardcoding a bunch of types as being Renderable, as in

data Renderable
  = Point Something
  | Line Something
  | Polygon Something

scene :: [Renderable]

Quite often an explicit ADT is much nicer. But they represent two opposing patterns of code-writing. Explicit ADT allows you to write case statements handling 'all the logic in one place'; a class forces you to separate the differences into 'separate instances'.

Both styles have their place. It's partly a question of taste, partly an issue of drawing your abstraction boundaries at different angles!

This is how you do it:

class CanRender a where render :: a -> RasterImage

data Renderable = forall a. (CanRender a) => Renderable a

scene :: [Renderable]

I even if I use those, I'll still have to wrap things up in a
constructor, won't I?

Yes. It's not as onerous as all that though. You can normally set things up with pleasant helper functions.

One helpful trick is to make the existential-type itself a member of the class, as in:

instance CanRender Renderable where render (Renderable a) = render a

I think it's interesting to note, in passing, that you don't have to use typeclasses. It can be perfectly useful to use existentials over other structures, like:

data Particle s = Particle { x :: Double,
                             y :: Double,
                             z :: Double,
                             state :: s,
                             drawme :: Particle s -> IO () }

...which is some representation of a 'particle' but it is parametric in some generic kind of state. Then if you don't care which state, you can do

data AnyParticle = forall s . Any (Particle s)

Including the last member 'drawme' makes this technique look rather like 'dictionaries-by-hand'. Which it is. And 'dictionaries-by-hand' is actually much nicer than type classes. The point is : just passing higher-order functions around in your data structures is a very useful trick. Taking existentials of these can be useful too. The *only* time it's useful to make this paradigm into a type-class, is when you want the compiler to automatically provide the dictionary for you, based on the types you use.

Since I can imagine several different kinds of particle which might use 'int' as state , but draw themselves differently, type-based dictionary choice is not the be-all and end-all. If you really really want to use type-based dictionary choice you can of course use newtypes... but it pays to remember there is a more elementary approach.


IF you draw the analogy between existentials and OO programming, then you might say the following:

Typeclass dictionaries are like class-based OO (e.g. Java) where to define a new kind of behaviour you need to make a new class.

Dictionaries-by-hand is like object-based OO (e.g. javascript) where to define a new kind of behaviour you can just change a method, one-off, for a particular object or bunch of objets.

However, I prefer to think that this is all about different kinds of abstraction. Java is at one extreme of the spectrum, with objects and classes being its only tools of abstraction (all code goes in classes, all data goes in objects). With Haskell the various parts of the abstraction are split up : custom data types, higher order functions, polymorphic functions, existential types. This means (with a little experience) you can apply the right tool for the job.

Jules

_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@xxxxxxxxxxx
http://www.haskell.org/mailman/listinfo/haskell-cafe

<Prev in Thread] Current Thread [Next in Thread>