Haskell中的异构多态(正确方式)

jos*_*uan 3 polymorphism haskell types class heterogeneous

让模块进行抽象Area操作(定义不好)

class Area someShapeType where
  area :: someShapeType -> Float

-- module utilities
sumAreas :: Area someShapeType => [someShapeType]
sumAreas = sum . map area
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

让一些数据

c1 = Circle (Point 0 0) 1
r1 = Rectangle (Point 0 0) (Point 1 1)
Run Code Online (Sandbox Code Playgroud)

然后,尝试使用

totalArea = sumAreas [c1, r1]
Run Code Online (Sandbox Code Playgroud)

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

我能做的使用forall 一个额外的data类型像这样

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

sumSurfaces :: [Shape] -> Float
sumSurfaces = sum . map (\(Shape x) -> surface x)
Run Code Online (Sandbox Code Playgroud)

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

sumSurfaces [Shape c1, Shape r1]
Run Code Online (Sandbox Code Playgroud)

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

在Haskell中进行"异构多态"的正确方法是什么?

非常感谢您的宝贵时间!

lef*_*out 8

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

关于存在量化是否好的存在争议,我认为大多数Haskeller不太喜欢它.在这里使用它当然不是正确的:sum [ area c1, area c2 ]更容易,也可以正常工作.但肯定会有更复杂的问题,它看起来不同; 当你"需要"异构多态时,那么存在就是你要走的路.

请记住,你总能解决这个问题:既然Haskell是懒惰的,你可以只应用所有可能的操作(在这个例子中只是area)"抢先",将所有结果存储在某些记录中,并输出这些记录的列表而不是多态对象列表.这样您就可以保留所有信息.

或者,这更加惯用,不要生成这样的对象列表.您希望对对象执行某些操作,那么为什么不将这些操作传递到您生成不同Shapes 的函数中,并将它们正确应用到位!这种逆转为普遍量化交换了存在量化,这种量化得到了更广泛的接受.

  • `data Shape = ...`怎么不直接?"可能会创建一个虚拟的`数据形状`",这几乎会破坏Haskell的所有类型推理机制.您需要为每个单独的对象提供一个显式类型,然后该语言看起来像C++ 03一样难看. (4认同)
  • @josejuan您可以使用惯用的Haskell方式,在这种情况下,您需要按照建议更改方法.或者你可以保持你的方法,并接受与语言的天然纹理背道而驰的尴尬. (3认同)

jbe*_*man 5

你的存在解决方案没问题.改为使用a可能更"漂亮" GADT,如:

{-# LANGUAGE GADTs #-}
data Shape where
    Shape :: (Surface a) => a -> Shape
Run Code Online (Sandbox Code Playgroud)

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

但我认为你基本上已经遇到了表达问题 ; 或者,或许,更准确地说:通过巧妙地构建代码(每个形状与类的单独类型)预期EP,你为自己引入了新的困难.

查看Wouter Swierstra提供的有趣数据类型,以获得我希望与您的问题相关的优雅解决方案.也许有人可以通过关于hackage的好包来评论那些受到该论文启发的启发.


Dmi*_*hus 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)
Run Code Online (Sandbox Code Playgroud)

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

*Main> map area [circle (Point 2 0) 5, rectangle (Point 0 0) (Point 2 10)]
[31.41592653589793,20.0]
Run Code Online (Sandbox Code Playgroud)