【发布时间】: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
我也有具体类型 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 的实例的异构列表,我遵循了 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, … }。像c和r这样的闭包捕获字段已经存在,但您也可以在没有类型类的情况下显式隐藏字段:data S where { S ∷ ∀ s. { fields ∷ s, draw ∷ s → IO (), move ∷ (Double, Double) → s → s, … } → S }。
标签: haskell existential-type heterogeneous heterogeneous-array