Values and Types
The type of Booleans¶
True is a value in Haskell. Its type is
Bool. In Haskell, we can state this as:
In Haskell, everything from simple values like
True to complex programs have a unique type.
Haskell types can be quite complex. To understand a type, always ask: what do the values belonging to this type look like?
For example, the values belonging to
The type of integers¶
Int is a type for integers, as in:
5 can have a more general type in Haskell. See here
The type of real numbers¶
There are several available options. A good general choice is
The type of text¶
Char is the type of single characters:
Text is the type of sequences of characters:
- See here for why this extension is needed.
The type of functions¶
A function in Haskell means the same as a function in mathematics: it takes an input and produces an output. The type of the function depends on the type of the input and the type of the output.
In Python, this would be written:
lambda x: x > 3
We can also define functions without the lambda syntax, like so:
Bool here are parameters of the type
Int -> Bool. One can obtain a different type by changing these parameters, e.g.
Text -> Int.
Product types (tuples)¶
Pairs of values are themselves values. For example
(True, False) has type
(Bool, Bool) is a type defined in terms of another type,
Bool. We could change either the left-hand or right-hand type, to get new types, like:
((Bool, Int), Bool)
If you have two types, say
Int, then you can generate a new type which is their disjoint union, called
Either Bool Int.
Leftis a function which takes
Trueas an argument. In other languages, this might be written
Rightis a function which takes
3as an argument. In other languages, this might be written
Right are functions.
- Actually, the type is more general:
forall a. a -> Either a Int. See the section on universally quantified types.
A closely related type is
Maybe, which in other languages is sometimes called
Justis a function of type
Bool -> Maybe Bool.
The most general type of
forall a. Maybe a: see the section on Universal types.
The unit type¶
() contains a single value, which is also written
Maybe X is the same as
Either () X (for any type
This practice of writing a type and a value with the same symbol is known as punning, and is quite widespread in Haskell. Be sure, when reading
() :: (), to understand that the
() on the left is a value and the
() on the right is a type.
The empty type¶
Void is the type with no values. It can be useful (mostly as a building block for more complex types), but at an introductory level is fairly rare.
- Nothing can go here except something like
undefined, which throws a runtime error.
The list type¶
The type of a list of
Bools is written
The type of a list of
Ints is written
More generally, for any type
[a] is the type of lists of values of type
Lists are homogeneous: all elements must have the same type.
Write a list as in Python, like
[True, False, True].
: is an operator to append to the front of a list. Examples:
[1,2,3] is just convenient syntax for
1 : (2 : (3 : )).
The IO type¶
IO Bool describes a process which can do arbitrary I/O, such as reading and writing to files, starting threads, running shell scripts, etc. The
Bool indicates that a result of running this process will be to produce a value of type
Bool. More generally, for any type
IO a runs a process and returns a value of type
If run, this will read a line from StdIn and this line will be the value of type
Text that is produced.
The top level function in a Haskell project is often:
Here is an example of polymorphism, or universal quantification over types:
Read this type as saying: for any type
a, and any type
b, this function will take a pair of values, one of type
a on the left, and one of type
b on the right, and give back a pair in the other order.
Specific types are always uppercase, but a variable ranging over types like
b above are always lowercase.
"any type" really means any type. That includes
[(Bool, Int)], functions like
(Int -> Bool) or
(Int -> Int) -> Bool, custom types you defined (e.g.
Either Bool [Int],
IO Int, and so on.
Universally quantified types are not like
Any in Python. For example, the Boolean negation function
not :: Bool -> Bool does not also have the type
a -> a.
forall a. (a, b) -> (b, a), both occurrences of
a must be the same, and both occurrences of
b must be the same. so
(Bool, Int) -> (Int, Bool) or
(Text, Double) -> (Double, Text), but not
(Bool, Int) -> (Double, Text).
For this reason, the only function that has type
forall a. a -> a is the identity function (written
id), because that is the only operation you can be sure works for every input type.
And no function has the type
forall a b. a -> b, because that function would need to be able to take an input of any type, and return an output of any type.
How to use¶
If you have a function with a universally quantified type as input, you can always call it on any particular types. For example:
If you have a non-function value of a universally quantified type, like undefined
:: forall a . a , you may use it as the argument to any function (although actually running the code will throw an error if
undefined is evaluated.)
Usage with parametrized types¶
Universally quantified types can appear as the parameters of other types:
The universally quantified
b indicate that
getLeft is only manipulating the structure of the input, but nothing more. For example, if a function like
not was called on
a could no longer be universally quantified:
Types for types¶
Types themselves have types, sometimes known as kinds.
> :kind Bool Bool :: * -- (1)! > :kind Int Int :: * > :kind (Either Bool Int) Either Bool Int :: * > :k Either Either :: * -> (* -> *) -- (2)! > :k (Either Bool) Either Bool :: (* -> *) > :k (Either Int) Either Int :: (* -> *) > :k [Bool] [Bool] :: * > :k (Bool, Int) (Bool, Int) :: * > :k   :: * -> *
*is the kind for all types that can have values, like
Either Bool Int,
[Bool]and so on.
- Consult this section if this is unclear. Note also that it will be displayed:
* -> * -> *by the repl.
The ability to have types of "higher kinds" (i.e. kinds like
* -> *, or
* -> * -> *) is a central feature that makes Haskell's type system more sophisticated than many languages.
In codebases, it is common to encounter types like
ReaderT which has kind
* -> (* -> *) -> * -> * or
Fix, which has kind
(* -> *) -> *
Universal quantification for other kinds than
Make sure to use the
GHC2021 extension or add the language extensions recommended by Haskell Language Server for this section.
In a universally quantified type like
forall a. a, we can explicitly specify the kind of types that the quantifier
forall ranges over:
The kind does not need to be
*. For example, here is the type of
fmap (see this section about typeclasses):
Created: January 7, 2023