Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

haskell polymorphism and lists

Suppose I have the following:

class Shape a where
    draw a :: a -> IO ()

data Rectangle = Rectangle Int Int

instance Shape Rectangle where
    draw (Rectangle length width) = ...

data Circle = Circle Int Int

instance Shape Circle where
    draw (Circle center radius) = ...

Is there any way for me to define a list of shapes, traverse over the list, and call the draw function on each shape? The following code won't compile because the list elements aren't all the same type:

shapes = [(Circle 5 10), (Circle 20, 30), (Rectangle 10 15)]

I know I'm thinking in an OO way and trying to apply it to Haskell, and that might not be the best approach. What would be the best Haskell approach for programs that need to deal with collections of different types of objects?

like image 833
Clint Miller Avatar asked Jun 15 '09 12:06

Clint Miller


People also ask

Does Haskell support polymorphism?

Polymorphism is widespread in Haskell and is a key feature of its type system. Most polymorphism in Haskell falls into one of two broad categories: parametric polymorphism and ad-hoc polymorphism.

Can lists in Haskell have different types?

One is of type (String,Int) , whereas the other is (Int,String) . This has implications for building up lists of tuples. We could very well have lists like [("a",1),("b",9),("c",9)] , but Haskell cannot have a list like [("a",1),(2,"b"),(9,"c")] . Which of these are valid Haskell, and why?

Are Haskell lists homogeneous?

Lists in Haskell must be type-homogeneous.

What is a Typeclass Haskell?

What's a typeclass in Haskell? A typeclass defines a set of methods that is shared across multiple types. For a type to belong to a typeclass, it needs to implement the methods of that typeclass. These implementations are ad-hoc: methods can have different implementations for different types.


1 Answers

If you really do need to do this, then use an existential:

{-# LANGUAGE GADTs #-}


class IsShape a where
    draw :: a -> IO ()

data Rectangle = Rectangle Int Int

instance IsShape Rectangle where
    draw (Rectangle length width) = ...

data Circle = Circle Int Int

instance IsShape Circle where
    draw (Circle center radius) = ...

data Shape where
    Shape :: IsShape a => a -> Shape

shapes = [Shape (Circle 5 10), Shape (Circle 20 30), Shape (Rectangle 10 15)]

(I renamed your class as there would be a name clash with the datatype otherwise, and having the naming this way round seems more natural).

The advantage of this solution over the other answer involving a single datatype with different constructors is that it is open; you can define new instances of IsShape wherever you like. The advantage of the other answer is that it's more "functional", and also that the closedness may in some cases be an advantage as it means that clients know exactly what to expect.

like image 55
GS - Apologise to Monica Avatar answered Sep 28 '22 12:09

GS - Apologise to Monica