【问题标题】:Heterogeneous polymorphism in Haskell (correct way)Haskell中的异构多态性(正确方法)
【发布时间】:2012-11-20 06:47:46
【问题描述】:

让一个模块抽象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][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 ShapeShape 构造函数(在 [Shape c1, ...] 和 lambda 参数上)的使用是丑陋的(我的第一个 [和糟糕的] 方式很漂亮)。

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

非常感谢您的宝贵时间!

【问题讨论】:

  • 嗯...我正在阅读 haskell.org/haskellwiki/Existential_type ,那么 data Shape 是正确的方式吗?
  • class Surface a => Area a where area = surface一样向class Area添加一个实例怎么样?

标签: class haskell types polymorphism heterogeneous


【解决方案1】:

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

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

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]

【讨论】:

  • 谢谢你,但你解决了我的例子,而不是我的问题(暴露抽象行为“先验”)。 jberryman 解释一下。无论如何,谢谢!
【解决方案2】:

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

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

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

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

查看 Wouter Swierstra 提供的有趣的 Data Types a la Carte,以获得我希望与您的问题相关的优雅解决方案。也许有人可以评论关于 hackage 的好包,看看受到那篇论文的启发。

【讨论】:

  • 哇! DataTypesALaCarte.pdf 正是我要找的,再次感谢!
【解决方案3】:

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

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

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

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

【讨论】:

  • 不,sum [ area c1, area c2 ] 不是有效响应,多态性未封装到所有进程中(总和...)。那是data Shape。我要求一种直接的方式(如果存在)来执行data Shape。是的,有可能,Haskell 编译器可以在检测到[c1, r1] 数组时创建一个虚拟的data Shape(我认为这与静态语言无关)。还是谢谢你!
  • data Shape = ... 怎么不是直接方式? “可以创建一个虚拟的data Shape”这会弄乱几乎所有 Haskell 的类型推断机制。你需要给每一个对象一个明确的类型,然后语言看起来就像 C++03 一样丑陋。
  • @josejuan 您可以使用惯用的 Haskell 方式进行操作,在这种情况下,您需要按照建议更改您的方法。或者你可以保持你的方法,接受违背语言自然规律的尴尬。
猜你喜欢
  • 1970-01-01
  • 2013-10-03
  • 2012-04-20
  • 2019-08-17
  • 1970-01-01
  • 1970-01-01
  • 2017-06-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多