Data Types
To create a new Data Type we use the data
keyword:
data Foo = Foo
x :: Foo
x = Foo
The type Foo
has one inhabitant, Foo
.
We can use the same name on the left, as the Data Type, and on the right as the Data Constructor because they are stored in different namespaces.
Product and Coproduct types
If some type can be either one or the other, but not both, it is a coproduct (sum type), also called union type.
In Set Theory, an union operation is an OR
operation.
The hello world of data types is defining our own version of true and false:
data Bool = T | F
f :: Bool
f = F
t :: Bool
t = T
Bool
is the data type and T
and F
are the data constructors (the two values that inhabit the Bool
type).
- A given `v
-
Bool` can be either
F
orT
but not both at the same time. In Set Theory, a union is an OR operation, generally denoted by|
or||
in programming languages.
data ReasonToCancel
= TooManyEmails
| NotInterested
| Other String
answer :: ReasonToCancel
answer = Other "I don't like email ads..."
The data constructor Other
in ReasonToCancel
data type is actually a function which implies:
Other :: String -> ReasonToCancel
That is, the function Other
is a function from String
to ReasonToCancel
.
It maps one type to another.
Type Variables
We can make the Other
data constructor take a type we don’t know yet, instead of hard-coding it as String
:
data WhyCancel a (1)
= TooManyEmails
| NotInterested
| Other a (2)
becauseWithString :: WhyCancel String (3)
becauseWithString = Other "I don't like email ads."
type Reason = { code :: Int, text :: String }
becauseWithReason :: Reason (4)
becauseWithReason =
{ code: 7
, text: "I don't like ads."
}
Note the a
type variable in 1 and 2.
It means when we type something as WhyCancel
, it takes a type variable, that is, some type, which is what we do in 3 and 4.
:kind
As explained in the book Haskell From First Principles:
Kinds are types one level up.
In other words, kinds are types of types. Try this in the REPL:
> import WhyCancel
> :kind WhyCancel
Type -> Type
> :kind WhyCancel Int
Type
In the first case, we get Type → Type
, which means WhyCancel
is not fully realised; it still requires some type to produce the final (concrete, realised) type.
In the second case, we get Type
, which means it has been fully realised and we are at a final, concrete type.
We can, of course, create a type alias for it:
> type T = WhyCancel String
> :kind T
Type
More examples
data Thing
= Foo
| Bar
| Sth String
sth :: Thing
sth = Sth "Takes a String"
Thing
is the data type, and Foo
, Bar
and Sth
are data constructors.
The data constructor Sth
takes String
.
Now, consider this:
type Jedi = { id :: Int, name :: String }
data Thing a
= Foo
| Bar
| Sth a
sthStr :: Thing String
sthStr = Sth "a string"
sthInt :: Thing Int
sthInt = Sth 1
sthJedi :: Thing Jedi
sthJedi = Sth { id: 1, name: "Ahsoka Tano" }
By making Thing
take a polymorphic type variable, we can construct data of different types.