Suppose I have the following:
class Shape a where
draw a :: a -> IO ()
data Rectangle = Rectangle Int Int
instance Shape Rectangle where
draw (Recta
One way to do it would be with vtables:
data Shape = Shape {
draw :: IO (),
etc :: ...
}
rectangle :: Int -> Int -> Shape
rectangle len width = Shape {
draw = ...,
etc = ...
}
circle :: Int -> Int -> Shape
circle center radius = Shape {
draw = ...,
etc = ...
}
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.
How to deal with a heterogeneous list of shapes in Haskell — Abstract polymorphism with type classes: http://pastebin.com/hL9ME7qP via @pastebin
CODE:
{-# LANGUAGE GADTs #-}
class Shape s where
area :: s -> Double
perimeter :: s -> Double
data Rectangle = Rectangle {
width :: Double,
height :: Double
} deriving Show
instance Shape Rectangle where
area rectangle = (width rectangle) * (height rectangle)
perimeter rectangle = 2 * ((width rectangle) + (height rectangle))
data Circle = Circle {
radius :: Double
} deriving Show
instance Shape Circle where
area circle = pi * (radius circle) * (radius circle)
perimeter circle = 2.0 * pi * (radius circle)
r=Rectangle 10.0 3.0
c=Circle 10.0
list=[WrapShape r,WrapShape c]
data ShapeWrapper where
WrapShape :: Shape s => s -> ShapeWrapper
getArea :: ShapeWrapper -> Double
getArea (WrapShape s) = area s
getPerimeter :: ShapeWrapper -> Double
getPerimeter (WrapShape s) = perimeter s
areas = map getArea list
perimeters = map getPerimeter list
As Ganesh said, you could indeed use GADTs to have more type safety. But if you don't want (or need) to, here's my take on this:
As you already know, all elements of a list need to be of the same type. It isn't very useful to have a list of elements of different types, because then your throwing away your type information.
In this case however, since you want throw away type information (you're just interested in the drawable part of the value), you would suggest to change the type of your values to something that is just drawable.
type Drawable = IO ()
shapes :: [Drawable]
shapes = [draw (Circle 5 10), draw (Circle 20 30), draw (Rectangle 10 15)]
Presumably, your actual Drawable
will be something more interesting than just IO ()
(maybe something like: MaxWidth -> IO ()
).
And also, due to lazy evaluation, the actual value won't be drawn until you force the list with something like sequence_
. So you don't have to worry about side effects (but you probably already saw that from the type of shapes
).
Just to be complete (and incorporate my comment into this answer): This is a more general implementation, useful if Shape
has more functions:
type MaxWith = Int
class Shape a where
draw :: a -> MaxWidth -> IO ()
size :: a -> Int
type ShapeResult = (MaxWidth -> IO (), Int)
shape :: (Shape a) => a -> ShapeResult
shape x = (draw x, size x)
shapes :: [ShapeResult]
shapes = [shape (Circle 5 10), shape (Circle 20 30), shape (Rectangle 10 15)]
Here, the shape
function transforms a Shape a
value into a ShapeResult
value, by simply calling all the functions in the Shape
class. Due to laziness, none of the values are actually computed until you need them.
To be honest, I don't think I would actually use a construct like this. I would either use the Drawable
-method from above, or if a more general solution is needed, use GADTs. That being said, this is a fun exercise.
Consider using a single type instead of separate types and a typeclass.
data Shape = Rectangle Int Int
| Circle Int Int
draw (Rectangle length width) = ...
draw (Circle center radius) = ...
shapes = [Circle 5 10, Circle 20 30, Rectangle 10 15]
A variant of Ganesh's solution using the existential quantification syntax instead.
{-# LANGUAGE ExistentialQuantification #-}
class IsShape a where
draw :: a -> String
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 = forall a. (IsShape a) => Shape a
shapes = [Shape (Circle 5 10), Shape (Circle 20 30), Shape (Rectangle 10 15)]