Control Flow in Haskell (2) - Variant
Tags: Haskell, Variant December 12, 2016

This post is part of a series.

Open sum type

With some type-level hackery, we can define an open sum type in GHC which has the same internal representation as other sum types such as Either:

The Word value is a tag (similar to a constructor tag) that indicates the actual type of the Any value by indexing into the types list. Given a type-level index n, we can get or set the value with the appropriate type:

It means that where we would have written:

we can now write:

Notice how we don’t have to declare a new data type to multiplex A, B and C into a single type.

Open sum type operations

setVariantN and getVariantN are the most basic operations, but we have many more of them:

  • set/get a variant value without specifying its index (the first matching type in the list is used):
  • prepend or append possible types to the variant type list:
  • lift a variant into another whose type list is a superset (in any order):
  • pick a value by index and get either a new variant or the picked value with the appropriate type:
  • catch any value of a given type and get either a new variant or the value with the appropriate type:
  • update a variant:
  • update a variant by inserting a variant:

Various uses of Variant

In the following part we will see how to use Variant to solve control flow issues. But Variant proves to be also very useful in other contexts. For instance to handle heterogeneous collections of items:

In addition, a Variant can easily make use of the type class instances of its members. For instance, Show, Eq and Ord are supported by default:

Defining new instances for Variant is easy and quite efficient:

With the INLINE pragmas and -O2, GHC 8.0.1 compiles the following code:

into the following core (in pseudo-code):

with my patch (see Trac #12877), GHC now produces the expected:

I have started to use this approach for a renderer which uses a Scene data type parameterized by the types of the objects it can contain:

The scene is abstract in the types of objects it can contain. We can use it as follows:

Compared to approaches with collections such as [forall a. Drawable a => a], we can get the objects out of the collection in a type-safe manner!

In the next part we see how to use Variant for control-flow.