Heterogeneous polymorphism in Haskell (correct way)

陌路散爱 提交于 2019-12-23 20:07:33

问题


Let a module to abstract Area operations (bad definition)

class Area someShapeType where
  area :: someShapeType -> Float

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

Let a posteriori explicit shape type modules (good or acceptable definition)

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)

Let some data

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

Then, trying to use

totalArea = sumAreas [c1, r1]

the [c1, r1] type must be expanded to [Circle] or [Rectangle]! (and is not valid)

I can do using forall and a extra data type like this

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

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

then, next code run successfully

sumSurfaces [Shape c1, Shape r1]

but I think, the use of data Shape and Shape constructor (on [Shape c1, ...] and lambda argument) is ugly (my first [and bad] way is pretty).

What is the correct way to do "Heterogeneous polymorphism in Haskell"?

Thank you very much for your time!


回答1:


Your existential solution is okay. It might be "prettier" to instead use a GADT, as in:

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

...and as leftaraoundabout suggests, you may be able to structure your code differently.

But I think you've basically hit up against the Expression Problem here; or perhaps, more accurately: by trying to structure your code cleverly (separate type for each shape with classes) in anticipation of the EP you've introduced new difficulties for yourself.

Check out the fun Data Types a la Carte by Wouter Swierstra for an elegant solution to what I hope is related to your problem. Maybe someone can comment with good packages on hackage to look at that are inspired by that paper.




回答2:


Your first (and bad) way is not pretty, it's Lispy. This is just not possible in a statically typed language; even when you do such a thing in e.g. Java you're actually introducing a seperate quantification step by using base class pointers, which is analoguous to the data Shape = forall a. Surface a.

There is dispute about whether existential quantification is nice, I think most Haskellers don't like it very much. It's certainly not the right thing to use here: sum [ area c1, area c2 ] is much easier and works just as well. But there sure are more complex problems where it looks differently; when you "need" heterogeneous polymorphism then existentials are the way to go.

Just remember that you can always get around this: since Haskell is lazy, you can just apply all possible operations (in this example it's only area) "pre-emptively", store all the results in some record, and output a list of these records instead of a list of polymorphic objects. This way you keep all the information.

Or, and that's more idiomatic, don't produce a list of such objects at all. You want to do something with the objects, so why not just pass these actions into the function where you produce different Shapes, and apply them right in place! This reversal exchanges existential quantification for universal quantification, which is rather more widely accepted.




回答3:


What you originally did is hit the existential antipattern.

Why use classes here anyways?

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)

And now you easily get what you want (a list of shapes):

*Main> map area [circle (Point 2 0) 5, rectangle (Point 0 0) (Point 2 10)]
[31.41592653589793,20.0]


来源:https://stackoverflow.com/questions/13672482/heterogeneous-polymorphism-in-haskell-correct-way

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!