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
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