15439

Heterogeneous polymorphism in Haskell (correct way)

Question:

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 <strong>posteriori</strong> 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 <strong>and</strong> 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 <em>"Heterogeneous polymorphism in Haskell"</em>?

Thank you very much for your time!

Answer1:

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 <a href="http://en.wikipedia.org/wiki/Expression_problem" rel="nofollow">Expression Problem</a> 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 <a href="http://lambda-the-ultimate.org/node/2700" rel="nofollow">Data Types a la Carte</a> 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.

Answer2:

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 <em>actions</em> 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.

Answer3:

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]

Recommend

  • Use/Need of Visitor Pattern in prog. lang. that support class extensions or open classes
  • using a union-like class in an std::initializer_list
  • What is the best way to deal with vim plugins on multiple machines? [closed]
  • Hash UUIDs without requiring ordering
  • Dynamically create AWS IoT topic
  • XGBOOST - DMATRIX
  • missing parameter name at index 0 {}
  • Python PIL remove sections of an image based on its colour
  • How to read JSON-LD data from HTML in Objective-C?
  • Angular2 & SystemJS : Cannot find module while building a moduleLoader
  • Convert symmetric matrix between packed and full storage?
  • Negated scanset in fscanf and EOF
  • Timeout a query
  • Receiver has no segue with identifier“***”
  • Using same constraints in multiple classes
  • C# - Most efficient way to iterate through multiple arrays/list
  • unable to get jsonEncode in magento2
  • netsh acl setting (need alternative method - registry settings?)
  • Implicit joins and Where in Doctrine - how?
  • Spring boot 2.0.0.M4 required a bean named 'entityManagerFactory' that could not be found
  • Web.config system.webserver errors
  • How can I set a binding to a Combox in a UserControl?
  • Remove final comma from string in vb.net
  • Create DicomImage from scratch using Dcmtk
  • Why value captured by reference in lambda is broken? [duplicate]
  • NetLogo BehaviorSpace - Measure runs using reporters
  • Perl system calls when running as another user using sudo
  • Function pointer “assignment from incompatible pointer type” only when using vararg ellipsis
  • 0x202A in filename: Why?
  • SVN: Merging two branches together
  • Hibernate gives error error as “Access to DialectResolutionInfo cannot be null when 'hibernate.
  • Benchmarking RAM performance - UWP and C#
  • python regex in pyparsing
  • Acquiring multiple attributes from .xml file in c#
  • Angular 2 constructor injection vs direct access
  • How to CLICK on IE download dialog box i.e.(Open, Save, Save As…)
  • Can Visual Studio XAML designer handle font family names with spaces as a resource?
  • File not found error Google Drive API
  • How can I remove ASP.NET Designer.cs files?
  • java string with new operator and a literal