3

让一个模块来抽象Area操作(不好的定义)

class Area someShapeType where
  area :: someShapeType -> Float

-- module utilities
sumAreas :: Area someShapeType => [someShapeType]
sumAreas = sum . map area

后验显式形状类型模块(良好或可接受的定义)

data Point = Point Float Float

data Circle = Circle Point Float
instance Surface Circle where
  surface (Circle _ r) = 2 * pi * r

data Rectangle = Rectangle Point Point
instance Surface Rectangle where
  surface (Rectangle (Point x1 y1) (Point x2 y2)) = abs $ (x2 - x1) * (y2 - y1)

放一些数据

c1 = Circle (Point 0 0) 1
r1 = Rectangle (Point 0 0) (Point 1 1)

然后,尝试使用

totalArea = sumAreas [c1, r1]

[c1, r1]类型必须扩展为[Circle]or [Rectangle]!(并且无效)

我可以使用这样forall 额外data类型

data Shape = forall a . Surface a => Shape a

sumSurfaces :: [Shape] -> Float
sumSurfaces = sum . map (\(Shape x) -> surface x)

然后,下一个代码成功运行

sumSurfaces [Shape c1, Shape r1]

但我认为,data ShapeandShape构造函数(on[Shape c1, ...]和 lambda 参数)的使用是丑陋的(我的第一个 [和坏的] 方式很漂亮)。

“Haskell中的异构多态性”的正确方法是什么?

非常感谢您的宝贵时间!

4

3 回答 3

8

你的第一个(也是坏的)方式并不漂亮,它是 Lispy。这在静态类型语言中是不可能的。即使你在例如 Java 中做这样的事情,你实际上是通过使用基类指针引入了一个单独的量化步骤,这类似于data Shape = forall a. Surface a.

关于存在量化是否好存在争议,我认为大多数 Haskellers 不太喜欢它。在这里使用它肯定不是正确的:sum [ area c1, area c2 ]它更容易并且效果也很好。但是肯定有更复杂的问题看起来不同。当您“需要”异构多态性时,存在主义就是要走的路。

area请记住,您总是可以解决这个问题:由于 Haskell 是惰性的,您可以“先发制人”地应用所有可能的操作(在这个例子中它只是),将所有结果存储在某个记录中,并输出这些记录的列表而不是多态对象的列表。这样您就可以保留所有信息。

或者,更惯用的是,根本不生成此类对象的列表。你想对对象做一些事情,那么为什么不把这些动作传递到你产生不同Shapes 的函数中,并在适当的地方应用它们!这种逆转将存在量化换成了普遍量化,后者被更广泛地接受。

于 2012-12-02T18:56:04.267 回答
5

你的存在主义解决方案没问题。改用 a 可能会“更漂亮” GADT,例如:

{-# LANGUAGE GADTs #-}
data Shape where
    Shape :: (Surface a) => a -> Shape

...正如 leftaraoundabout 建议的那样,您可以以不同的方式构建代码。

但我认为你在这里基本上遇到了表达问题;或者,也许更准确地说:通过尝试巧妙地构造代码(为每个形状与类分开类型)以期待 EP,您为自己引入了新的困难。

查看 Wouter Swierstra 的有趣的点菜数据类型,以获得我希望与您的问题相关的优雅解决方案。也许有人可以用关于 hackage 的优秀软件包发表评论,看看受到那篇论文的启发。

于 2012-12-02T22:16:33.737 回答
5

您最初所做的是触及存在主义反模式。

为什么还要在这里使用类?

data Shape = Shape { area :: Double }

data Point = Point Double Double

circle :: Point -> Double -> Shape
circle p r =
    Shape $ 2 * pi * r

rectangle :: Point -> Point -> Shape
rectangle (Point x1 y1) (Point x2 y2) =
    Shape $ abs $ (x2 - x1) * (y2 - y1)

现在你很容易得到你想要的(形状列表):

*Main> map area [circle (Point 2 0) 5, rectangle (Point 0 0) (Point 2 10)]
[31.41592653589793,20.0]
于 2012-12-02T23:08:03.043 回答