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?
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.
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?
Lists in Haskell must be type-homogeneous.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With