Проблема компиляции с экзистенциальными типами в Haskell
Я написал простой класс типов
Shape
:
class Shape a where
draw :: a -> IO ()
move :: (Double,Double) -> a -> a
area :: a -> Double
circum :: a -> Double
У меня также есть конкретные типы
Circle
,
Rect
и
Triangle
которые создают экземпляр этого класса типов следующим образом:
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)
Для работы с разнородными списками, содержащими экземпляры конкретных типов
Circle
,
Rect
и
Triangle
Я следил за учебником haskell вики по гетерогенным коллекциям и реализовал экзистенциальный тип данных
ShapeType
вот так:
{-# LANGUAGE ExistentialQuantification #-}
data ShapeType = forall a . Shape a => MkShape a
Я позволяю
ShapeType
создать экземпляр
Shape
тип класс:
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
Теперь я могу использовать это в коде следующим образом:
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
К сожалению, это компилируется, только если я опускаю строку
move (x,y) (MkShape s) = move (x,y) s
В противном случае я получаю следующую ошибку компиляции:
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)
Для меня это не имеет смысла, так как "извлечение"
s
путем сопоставления с образцом для использования в вызовах делегирования отлично работает в трех других случаях.
Есть идеи, что здесь не так?
Обновить
Благодаря этому простому исправлению код теперь работает должным образом:
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)
1 ответ
Вы упустили конструктор. Тебе нужно
move v (MkShape s) = MkShape $ move v s
Я не уверен, что ваш подход здесь действительно лучший; экзистенциальные типы имеют тенденцию просто склеивать дела в подобной ситуации. Вы должны хотя бы рассмотреть простой старый тип суммы. Экзистенциальные возможности бесценны для некоторых целей, но в других контекстах они активно вредны.