When defining a union type, you list all the ways to construct a value of that type. In its simplest form, that definition looks like this:
type Visibility
= All
| Active
| Completed
As you've surmised, this declares the type Visibility
and defines three values, all of type Visibility
. The only way to construct a value of type Visibility
is to use one of these three options. Because of that, we often call them "constructors."
Here is a slightly more complicated union type definition:
type TrainStatus
= OnTime
| Delayed Int
As you would expect, this defines two new "constructors," OnTime
and Delayed
. But look at their types:
OnTime : TrainStatus
Delayed : Int -> TrainStatus
The OnTime
constructor takes zero arguments, and so is simply a value; it is already a TrainStatus
. But Delayed
is declared as a one-argument constructor: it is a function that creates a new TrainStatus
out of an Int
. As such, Delayed 5
, Delayed 10
, and Delayed 100
are all valid TrainStatus
values. (We can interpret them as "delayed by 5 minutes" or something similar.)
A constructor can take multiple arguments; for instance, if we'd like to include, as a String, a reason for a delay:
type TrainStatus
= OnTime
| Delayed Int String
ts : TrainStatus
ts = Delayed 20 "The conductor took a short nap."
which defines Delayed : Int -> String -> TrainStatus
.
If you're given a TrainStatus
, you can extract the Int
and String
inside of it using pattern matching:
case ts of
OnTime ->
"Your train is on time!"
Delayed minutes reason ->
"Your train has been delayed by " ++ toString minutes ++ " because " ++ reason