1. Introduction

1.1. Why do we need Variant?

In the functional programming world we use algebraic data types (ADT), more specifically sum types, to indicate that a value can be of two or more different types:

x,y :: Either String Int
x = Left "yo"
y = Right 10

What if we want to support more than two types?

1.1.1. Solution 1: sum types

We could use different sum types with different constructors for each arity (number of different types that the value can have).

data SumOf3 a b c   = S3_0 a | S3_1 b | S3_2 c
data SumOf4 a b c d = S4_0 a | S4_1 b | S4_2 c | S4_3 d

But it’s quite hard to work with that many different types and constructors as we can’t easily define generic functions working on different sum types without a combinatorial explosion.

1.1.2. Solution 2: recursive ADT

Instead of adding new sum types we can use a nest of Either:

type SumOf3 a b c   = Either a (Either b c)
type SumOf4 a b c d = Either a (Either b (Either c d))

Or more generically:

data Union (as :: [*]) where
  Union :: Either (Union as) a -> Union (a ': as)

This time we can define generic functions without risking a combinatorial explosion. The drawback however is that we have changed the representation: instead of tag + value where tag is in the range [0,arity-1] we have a nest of tag + (tag + (... (tag + value))) where tag is in the range [0,1]. It is both inefficient in space and in time (accessing the tag value is in O(arity)).

1.1.3. Solution 3: variant

Variant gets the best of both approaches: it has the generic interface of the “recursive ADT” solution and the efficient representation of the “sum types” solution.

data Variant (types :: [*]) = Variant {-# UNPACK #-} !Word Any

type role Variant representational

The efficient representation is ensured by the definition of the Variant datatype: an unpacked Word for the tag and a “pointer” to the value.

The phantom type list types contains the list of possible types for the value. The tag value is used as an index into this list to know the effective type of the value.

1.2. Using Variant

To use Variant:

  • add a dependency to the haskus-utils-variant package
  • use the following import: import Haskus.Utils.Variant

You may need to enable some language extensions:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeFamilies #-}