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