[Haskell-cafe] Polymorphic (typeclass) values in a list?
Jules Bean
jules at jellybean.co.uk
Fri Oct 19 12:32:10 EDT 2007
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
More information about the Haskell-Cafe
mailing list