Проблема компиляции с экзистенциальными типами в 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

Я не уверен, что ваш подход здесь действительно лучший; экзистенциальные типы имеют тенденцию просто склеивать дела в подобной ситуации. Вы должны хотя бы рассмотреть простой старый тип суммы. Экзистенциальные возможности бесценны для некоторых целей, но в других контекстах они активно вредны.

Другие вопросы по тегам