Heterogeneous polymorphism in Haskell (correct way)


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!


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.


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.


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]


  • 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
  • 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