Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Existential types in Haskell and generics in other languages

I was trying to grasp the concept of existential types in Haskell using the article Haskell/Existentially quantified types. At the first glance, the concept seems clear and somewhat similar to generics in object oriented languages. The main example there is something called "heterogeneous list", defined as follows:

data ShowBox = forall s. Show s => SB s
 
heteroList :: [ShowBox]
heteroList = [SB (), SB 5, SB True]

instance Show ShowBox where
  show (SB s) = show s
 
f :: [ShowBox] -> IO ()
f xs = mapM_ print xs

main = f heteroList

I had a different notion of a "heterogeneous list", something like Shapeless in Scala. But here, it's just a list of items wrapped in an existential type that only adds a type constraint. The exact type of its elements is not manifested in its type signature, the only thing we know is that they all conform to the type constraint.

In object-oriented languages, it seems very natural to write something like this (example in Java). This is a ubiquitous use case, and I don't need to create a wrapper type to process a list of objects that all implement a certain interface. The animals list has a generic type List<Vocal>, so I can assume that its elements all conform to this Vocal interface:

interface Vocal {

    void voice();
}

class Cat implements Vocal {

    public void voice() {
        System.out.println("meow");
    }
}

class Dog implements Vocal {

    public void voice() {
        System.out.println("bark");
    }
}

var animals = Arrays.asList(new Cat(), new Dog());
animals.forEach(Vocal::voice);

I noticed that existential types are only available as a language extension, and they are not described in most of the "basic" Haskell books or tutorials, so my suggestion is that this is quite an advanced language feature.

My question is, why? Something that seems basic in languages with generics (constructing and using a list of objects whose types implement some interface and accessing them polymorphically), in Haskell requires a language extension, custom syntax and creating an additional wrapper type? Is there no way of achieving something like that without using existential types, or is there just no basic-level use cases for this?

Or maybe I'm just mixing up the concepts, and existential types and generics mean completely different things. Please help me make sense of it.

like image 657
Sergei Avatar asked Feb 18 '21 06:02

Sergei


People also ask

What is an existential type?

Existential types allow us to create abstract data types: stacks, queues, maps, heaps, etc. More generally, they allow one to create any new type they like that doesn't already exist in the base language, in terms of other types.

What is an existential type in Swift?

An existential type is a tuple of the underlying value and the operations we can perform on that value. (value, operations:Num) So although we've lost all information about the underlying type, we've gained a set of operations we can perform on the type.

What are existential data types in Haskell?

In Haskell, an existential data type is one that is defined in terms not of a concrete type, but in terms of a quantified type variable, introduced on the right-hand side of the data declaration. This is, as is the case for so many Haskell concepts, not a particularly helpful definition in the abstract.

Why do we need generics in Haskell?

This way we can declare how to perform a computation on almost any data type. Haskell features that make generics possible are type classes and ad-hoc polymorphism. The ability to describe a data type in terms of a set of combinators is our property, captured by the Generic type class.

What is an existential type?

Working around this takes a judicious application of an existential type. In Haskell, an existential data type is one that is defined in terms not of a concrete type, but in terms of a quantified type variable, introduced on the right-hand side of the data declaration.

What programming language is used in Haskell?

Now to relate it back to Haskell and programming in general. In Haskell, by default there are 2 languages. The term level language and the type level language. Note that the type level language in Haskell resembles a limited logic programming language.


1 Answers

Yes,existential types and generic mean different things. An existential type can be used similarly to an interface in an object-oriented language. You can put one in a list of course, but a list or any other generic type is not needed to use an interface. It is enough to have a variable of type Vocal to demonstrate its usage.

It is not widely used in Haskell because it is not really needed most of the time.

nonHeteroList :: [IO ()]
nonHeteroList = [print (), print 5, print True]

does the same thing without any language extension.

An existential type (or an interface in an object-oriented language) is nothing but a piece of data with a bundled dictionary of methods. If you only have one method in your dictionary, just use a function. If you have more than one, you can use a tuple or a record of those. So if you have something like

interface Shape {
   void Draw();
   double Area();
}

you can express it in Haskell as, for example,

type Shape = (IO (), Double)

and say

circle center radius = (drawCircle center radius, pi * radius * radius)
rectangle topLeft bottomRight = (drawRectangle topLeft bottomRight, 
           abs $ (topLeft.x-bottomRight.x) * (topLeft.y-bottomRight.y))

shapes = [circle (P 2.0 3.5) 4.2, rectangle (P 3.3 7.2) (P -2.0 3.1)]

though you can express exactly the same thing with type classes, instances and existentials

class Shape a where
  draw :: a -> IO ()
  area :: a -> Double

data ShapeBox = forall s. Shape s => SB s
instance Shape ShapeBox where
  draw (SB a) = draw a
  area (SB a) = area a

data Circle = Circle Point Double
instance Shape Circle where
  draw (Circle c r) = drawCircle c r
  area (Circle _ r) = pi * r * r

data Rectangle = Rectangle Point Point
instance Shape Rectangle where
  draw (Rectangle tl br) = drawRectangle tl br
  area (Rectangle tl br) = abs $ (tl.x - br.x) * (tl.y - br.y)

shapes = [Circle (P 2.0 3.5) 4.2, Rectangle (P 3.3 7.2) (P -2.0 3.1)]

and there you have it, N times longer.

like image 132
n. 1.8e9-where's-my-share m. Avatar answered Nov 07 '22 14:11

n. 1.8e9-where's-my-share m.