WARNING: what I describe here seems to be an antipattern
See Haskell Antipattern: Existential Typeclass
So better only use it with what is said in the Haskell-Wiki and mind the good comments bellow – Thank you Stephen and Jacob
Sometimes things you take for granted in your common OOP language (says C#) seems to be hard or impossible in Haskell at first glance.
This is no big deal – indeed the problem is so simple I thought for a moment I must have lost my mind over night as I could find no easy way to solve this in Haskell at first – and indeed the solution is quite simple but it involves a language extension I had not seen before (yeah – I’m still in my Haskell-noob-years – ask me again another decade or so ) but maybe this article can help some fellow beginner.
In this post I’m going to describe a possibility to access a list of animals (data) of different types that share some common traits like the number of limbs.
To see what I want look at the following short C# code:
interface IAnimal { int NumberOfLimbs { get; } } class Dog : IAnimal { public string Name { get; set; } public string Color { get; set; } public int NumberOfLimbs { get { return 4; } } } class Spider : IAnimal { public int NumberOfLimbs { get { return 8; } } } static class Animals { public static void DescribeAnimals() { var animals = new IAnimal[] {new Dog {Name = "Mozart"}, new Spider()}; foreach(var animal in animals) Console.WriteLine("This one has {0} limbs", animal.NumberOfLimbs); } }
The purpose of this is just to get a array/list of animals – you don’t even have to use a interface – a base class would serve the same purpose.
Easy right?
How hard can this be in Haskell?
I came upon this problem while trying to write a simple ray-tracer where a scene consists of a collection of renderable objects. Of course this is easy if you go and use an algebraic data-type for this. But I wanted the raytracer to be open (you want to provide some object other then spheres and polygons – go on: add them) – but of course ADTs are closed – so no luck there.
Of course the next thing you are thinking of are data and type-classes and indeed you can solve the problem in this way but it’s not as easy as it might seem.
Let’s get back to the example above – an open way to implement the NumberOfLimbs property could look like this:
module Animals where class IBody a where nrLimbs :: a -> Int newtype Dog = Dog String instance IBody Dog where nrLimbs (Dog _) = 4 newtype Spider = Spider () instance IBody Spider where nrLimbs (Spider ()) = 8
But how can we get a list of things with different data-types (Dog and Spider) ? – Well we don’t – a list is a homogenous data-structure and IBody is not a data-type but a type-constructor so we have to say what ‘a’ is.
So we have to get a data-type that somehow can a instance of the IBody class without having to say what the data-type really is. Well you cannot do this in vanilla Haskell but we are not out of luck: there is an language-extension named “ExistentialQuantification” that can help us here: it gives you a “forall” quantifier just like in math that allows you to remove a concrete data type from your data-definition.
You can read a much better explanation than I could give here (HaskellWiki: Existential ypes) but here is what this can look like in our simple example:
data Animal = forall a. (IBody a) => Animal a describeAnimals :: [Animal] -> [String] describeAnimals [] = [] describeAnimals (a:as) = describe a : describeAnimals as where describe (Animal a) = "This one has " ++ (show $ nrLimbs a) ++ " limbs"
As you can see I added an Animal data-type whose constructor can wrap every IBody-Instance, but you don’t have to constraint it to any concrete type a because it’s “forall” those.
Of course now the compiler cannot know anything other of such an Animal besides that it’s of instance IBody – but that is exactly what we want.
The describeAnimals function finally takes advantage of this and can handle list of Animals, and you could use it like this:
> let animals = [Animal (Dog "Mozart"), Animal (Spider ())] > describeAnimals animals ["This one has 4 limbs","This one has 8 limbs"]
Of course this is not the hole story and I encourage you to read the very good wiki article I linked above.