Types without values

Joël Quenneville

One helpful way to think about types is to consider its cardinality — that is, how many possible distinct values does it have?

For example in Elm, this custom Direction type has exactly 4 distinct values that can be constructed (you could say it has a cardinality of 4).

type Direction
  = North
  | South
  | East
  | West

Weathervane Photo by Jordan Ladikos on Unsplash

Similarly, we can say the Bool type has two distinct values (True and False). The empty tuple () type has single possible value - an empty tuple.

We’ve seen it’s possible to create types with cardinalities of 4, 2, and 1. You may wonder, is it possible to create a type that has zero possible values?

Never

Elm has a type Never that does just that. The type name Never can still be used in function signatures but it’s impossible to actually create a value of type Never.

This is done with a clever trick. The definition looks like this:

type Never = JustOneMore Never

This type is defined recursively. Because there is no base case, constructing a value of type Never is an infinite job.

aNeverValue : Never
aNeverValue =
  JustOneMore (JustOneMore (JustOneMore ...) )

Uses

So it’s possible to create a type with zero values. Is this more than just a curiosity? Is it actually useful? Yes.

The Never type is really valuable when trying to constrain the signatures of other types. For example Task.perform from elm/core:

perform : (a -> msg) -> Task Never a -> Cmd msg

This signature says that perform can only be called on tasks that cannot fail. All other tasks have to use the attempt function that will force you to do some error handling. That’s a really useful distinction to be able to make!

Use with phantom types

Phantom types are types that declare a type parameter but never use it. For example:

-- The `a` is never used anywhere

type Currency a
  = Currency Int

We would like to be able to create types like Currency Dollar or Currency Euro. We could create our own Dollar or Euro types. Since we only care about the type in the signature and not in the body, we can do some fanciness to create types that can never be instantiated.

To accomplish this we can define these types as infinitely recursive, similar to Elm’s Never type. Just like Never, Dollar and Euro can never be instantiated.

-- These two types are infinitely recursive.
-- Similar to `Never`, they can never be instantiated.

type Dollar
  = OneMoreDollar Dollar

type Euro
  = OneMoreEuro Euro