2. Basics

EADTs can be found in the haskus-utils-variant package.

You need the following imports in your source:

import Haskus.Utils.EADT
import Haskus.Utils.EADT.TH -- template-haskell helpers

2.1. Defining constructors

EADT constructors are data types that must have a Functor type-class instance. Fortunately defining such data types is easy thanks to the DeriveFunctor extension that automatically generates the Functor instance for us.

For instance, let’s define the constructors for a list:

{-# LANGUAGE DeriveFunctor #-}

data ConsF a e = ConsF a e deriving (Functor)
data NilF    e = NilF      deriving (Functor)

Note that both data types are parameterised by e even if e isn’t used in NilF definition.

2.2. Defining pattern synonyms

We can match EADT values with the VF pattern synonym. To make the use of EADTs more pleasant, it is highly recommended to define an additional pattern synonym for each constructor:

pattern Cons :: ConsF a :<: xs => a -> EADT xs -> EADT xs
pattern Cons a l = VF (ConsF a l)

pattern Nil :: NilF :<: xs => EADT xs
pattern Nil = VF NilF

These patterns hide the use of the VF pattern and make the code much easier to work with.

As this code is very straightforward to write, we provide Template-Haskell helpers to generate them automatically. The previous patterns can be generated with:

{-# LANGUAGE TemplateHaskell #-}

import Haskus.Utils.EADT.TH

eadtPattern 'ConsF "Cons"
eadtPattern 'NilF  "Nil"

2.3. Defining the EADT

An EADT is just a type alias as in the following List EADT example:

type List a = EADT '[ConsF a, NilF]

2.4. Creating values

Thanks to the pattern synonyms defined above, we can define values as we would with a normal ADT:

strList :: List String
strList = Cons "How" (Cons "are" (Cons "you?" Nil))

In some cases we have to help the type-checker to determine some types. For instance, in the following example it can’t infer the a type in ConsF a, hence we have to use type ascriptions:

intList :: List Int
intList = Cons (10 :: Int) $ Cons (20 :: Int) $ Cons (30 :: Int) Nil

This is because the code is generic enough that the same pattern synonyms could be used to build an heterogeneous list. For instance containing both Int and Float:

mixedList :: EADT '[ConsF Int, ConsF Float, NilF]
mixedList = Cons (10 :: Int) $ Cons (5.0 :: Float) $ Cons (30 :: Int) Nil

We could also easily define another pattern synonym when we work on List to help the inference algorithm:

-- pattern for a specific EADT: List a
pattern ConsList :: a -> List a -> List a
pattern ConsList a l = Cons a l

We can see that when we use it we don’t need type ascriptions because the Int type is propagated:

intList :: List Int
intList = ConsList 10 $ ConsList 20 $ ConsList 30 Nil

2.5. Matching values

It is easy and tempting to use the same pattern synonyms to match EADT values. And indeed this works pretty well:

showEADTList :: Show a => List a -> String
showEADTList = \case
   ConsList a l -> show a ++ " : " ++ showEADTList l
   Nil          -> "Nil"
   _            -> undefined

> putStrLn (showEADTList strList)
"How" : "are" : "you?" : Nil

> putStrLn (showEADTList intList)
10 : 20 : 30 : Nil

However this approach is a unsatisfactory for two reasons:

  1. The pattern matching isn’t safe: for now the compiler cannot use the EADT constructor type list to infer that the pattern-match is complete. Hence we need the wildcard match to avoid a warning and to use ConsList to help the type inference. A better alternative is presented in the safe pattern-matching chapter.

  2. The function isn’t generic: if we would like to write a showEADTList function that also works on the heterogeneous mixedList above or on any future EADT provided its constructors can be handled, we need to use another approach based on type-classes. This is presented in the following chapters.