【问题标题】:Compilation issue with existential types in HaskellHaskell 中存在类型的编译问题
【发布时间】:2021-03-11 18:00:11
【问题描述】:

我写了一个简单的类型类Shape

class Shape a where
  draw   :: a -> IO ()
  move   :: (Double,Double) -> a -> a
  area   :: a -> Double
  circum :: a -> Double

我也有具体类型 CircleRectTriangle 像这样实例化这个类型类:

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)

为了使用包含具体类型 CircleRectTriangle 的实例的异构列表,我遵循了 the haskell wiki tutorial on heterogenous collections 并实现了一个存在数据类型 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)

【问题讨论】:

  • 这里存在的一个很好的替代方法是函数记录:data S = S { draw ∷ IO (), move ∷ (Double, Double) → S, … }。形状被定义为行为的结构组合,而不是类型类中的名义类型:circle c r = S { draw = putStrLn "Circle" …, move = \(x, y) → circle (movePoint x y c) r, … }。像 cr 这样的闭包捕获字段已经存在,但您也可以在没有类型类的情况下显式隐藏字段:data S where { S ∷ ∀ s. { fields ∷ s, draw ∷ s → IO (), move ∷ (Double, Double) → s → s, … } → S }

标签: haskell existential-type heterogeneous heterogeneous-array


【解决方案1】:

你错过了一个构造函数。你需要

move v (MkShape s) = MkShape $ move v s

我不太相信你的方法真的是最好的;在这种情况下,存在主义类型往往只会把作品搞砸。您至少应该考虑一个普通的旧 sum 类型。存在主义在某些方面是无价的,但在其他情况下它们是有害的。

【讨论】:

  • 非常感谢!多么愚蠢的错误……我不知何故被编译器消息弄糊涂了……
  • 也感谢您对更好地使用简单的 sum 类型的评论。
  • 什么时候存在类型是无价的?
  • @is7s,我最近的使用涉及使用非常规数据类型表示的惰性二项式队列的去摊销。更有趣的用途包括类型对齐序列、免费Applicative 函子的一些表示、lensbifunctors 中的遍历捕获机制以及Coyoneda 类型。当存在类型是一个好主意时,我没有任何经验法则。它们只是不时弹出。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-23
  • 2013-10-19
  • 1970-01-01
  • 1970-01-01
  • 2018-04-03
  • 2013-01-28
  • 1970-01-01
相关资源
最近更新 更多