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 Shape和Shape构造函数(on [Shape c1, ...]和lambda参数)是丑陋的(我的第一个[和坏]方式很漂亮).
在Haskell中进行"异构多态"的正确方法是什么?
非常感谢您的宝贵时间!
你的第一个(也是坏的)方式并不漂亮,它是Lispy.这在静态类型语言中是不可能的; 即使你在例如Java中做这样的事情,你实际上是通过使用基类指针引入一个单独的量化步骤,这类似于data Shape = forall a. Surface a.
关于存在量化是否好的存在争议,我认为大多数Haskeller不太喜欢它.在这里使用它当然不是正确的:sum [ area c1, area c2 ]更容易,也可以正常工作.但肯定会有更复杂的问题,它看起来不同; 当你"需要"异构多态时,那么存在就是你要走的路.
请记住,你总能解决这个问题:既然Haskell是懒惰的,你可以只应用所有可能的操作(在这个例子中只是area)"抢先",将所有结果存储在某些记录中,并输出这些记录的列表而不是多态对象列表.这样您就可以保留所有信息.
或者,这更加惯用,不要生成这样的对象列表.您希望对对象执行某些操作,那么为什么不将这些操作传递到您生成不同Shapes 的函数中,并将它们正确应用到位!这种逆转为普遍量化交换了存在量化,这种量化得到了更广泛的接受.
你的存在解决方案没问题.改为使用a可能更"漂亮" GADT,如:
{-# LANGUAGE GADTs #-}
data Shape where
Shape :: (Surface a) => a -> Shape
Run Code Online (Sandbox Code Playgroud)
......正如leftaraoundabout建议的那样,您可能能够以不同的方式构建代码.
但我认为你基本上已经遇到了表达问题 ; 或者,或许,更准确地说:通过巧妙地构建代码(每个形状与类的单独类型)预期EP,你为自己引入了新的困难.
查看Wouter Swierstra提供的有趣数据类型,以获得我希望与您的问题相关的优雅解决方案.也许有人可以通过关于hackage的好包来评论那些受到该论文启发的启发.
你最初做的是击中存在主义的反模式.
为什么要在这里使用课程?
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)