I've written a simple typeclass Shape
:
class Shape a where
draw :: a -> IO ()
move :: (Double,Double) -> a -> a
area :: a -> Double
circum :: a -> Double
I also have concrete types Circle
, Rect
and Triangle
that instantiate this typeclass like so:
data Circle = Circle Point Double deriving (Show)
instance Shape Circle where
draw (Circle centre radius) = putStrLn $ "Circle [" ++ show centre ++ ", " ++ show radius ++ "]"
move (x,y) (Circle centre radius) = Circle (movePoint x y centre) radius
area (Circle _ r) = r ^ 2 * pi
circum (Circle _ r) = 2 * r * pi
movePoint :: Double -> Double -> Point -> Point
movePoint x y (Point x_a y_a) = Point (x_a + x) (y_a + y)
In order to work with heterogenous lists containing instances of the concrete types Circle
, Rect
and Triangle
I followed the haskell wiki tutorial on heterogenous collections and implemented an existential data type ShapeType
like so:
{-# LANGUAGE ExistentialQuantification #-}
data ShapeType = forall a . Shape a => MkShape a
I let ShapeType
instantiate the Shape
type class:
instance Shape ShapeType where
area (MkShape s) = area s
circum (MkShape s) = circum s
draw (MkShape s) = draw s
move (x,y) (MkShape s) = move (x,y) s -- this does not compile
Now I can use this in code like follows:
rect = Rect (Point 0 0) (Point 5 4)
circle = Circle (Point 4 5) 4
triangle = Triangle (Point 0 0) (Point 4 0) (Point 4 3)
shapes :: [ShapeType]
shapes = [MkShape rect, MkShape circle, MkShape triangle]
main = do
print $ map area shapes
print $ map circum shapes
mapM_ draw shapes
Unfortunately this only compiles if I ommit the line
move (x,y) (MkShape s) = move (x,y) s
Otherwise I get the following compilation error:
error:
* Couldn't match expected type `ShapeType' with actual type `a'
`a' is a rigid type variable bound by
a pattern with constructor:
MkShape :: forall a. Shape a => a -> ShapeType,
in an equation for `move'
at C:\\workspace\FPvsOO\src\Lib.hs:102:15-23
* In the expression: move (x, y) s
In an equation for `move': move (x, y) (MkShape s) = move (x, y) s
In the instance declaration for `Shape ShapeType'
* Relevant bindings include
s :: a (bound at C:\\workspace\FPvsOO\src\Lib.hs:102:23)
This doesn't make sense to me, as "extracting" the s
by pattern matching for usage in the delegating calls works fine in the other three cases.
Any ideas what's going wrong here?
Update
With this simple fix the code now works as expected:
instance Shape ShapeType where
area (MkShape s) = area s
circum (MkShape s) = circum s
draw (MkShape s) = draw s
move vec (MkShape s) = MkShape (move vec s)
You missed a constructor. You need
move v (MkShape s) = MkShape $ move v s
I'm not so convinced your approach here is really best; existential types tend to just gum up the works in this sort of situation. You should at least consider a plain old sum type. Existentials are invaluable for some purposes, but in other contexts they're actively harmful.