Values and Types
The type of Booleans¶
True
is a value in Haskell. Its type is Bool
. In Haskell, we can state this as:
Similarly,
Note
In Haskell, everything from simple values like True
to complex programs have a unique type.
Tip
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 Bool
are True
and False
.
The type of integers¶
Int
is a type for integers, as in:
Gotcha
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 Double
:
The type of text¶
Char
is the type of single characters:
Text
is the type of sequences of characters:
{-# LANGUAGE OverloadedStrings #-} --(1)!
import Data.Text (Text)
exampleText :: Text
exampleText = "hello world!"
- 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.
Note
In Python, this would be written: lambda x: x > 3
We can also define functions without the lambda syntax, like so:
!! Note
Int
and 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)
:
Note
(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)
(Int, Bool)
((Bool, Int), Bool)
Sum types¶
If you have two types, say Bool
and Int
, then you can generate a new type which is their disjoint union, called Either Bool Int
.
(Left True) :: Either Bool Int -- (1)!
(Left False) :: Either Bool Int
(Right 3) :: Either Bool Int -- (2)!
(Right 7) :: Either Bool Int
-
Left
is a function which takesTrue
as an argument. In other languages, this might be writtenLeft(True)
-
Right
is a function which takes3
as an argument. In other languages, this might be writtenRight(3)
Note
Left
and Right
are functions.
- Actually, the type is more general:
forall a. a -> Either a Int
. See the section on universally quantified types.
Maybe¶
A closely related type is Maybe
, which in other languages is sometimes called Optional
:
Just True :: Maybe Bool -- (1)!
Just 5 :: Maybe Int
Nothing :: Maybe Bool
-- Also true:
Nothing :: Maybe Int -- (2)!
-
Just
is a function of typeBool -> Maybe Bool
. -
The most general type of
Nothing
isforall a. Maybe a
: see the section on Universal types.
The unit type¶
The type ()
contains a single value, which is also written ()
.
Note
Conceptually, Maybe X
is the same as Either () X
(for any type X
).
Warning
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 Bool
s is written [Bool]
or [] Bool
.
The type of a list of Ints
is written [Int]
or [] Int
.
More generally, for any type a
, [a]
is the type of lists of values of type a
.
Gotcha
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:
Note
[1,2,3]
is just convenient syntax for 1 : (2 : (3 : []))
.
The IO type¶
The 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 a
, IO a
runs a process and returns a value of type a
.
An example:
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:
Universal types¶
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 a
and b
above are always lowercase.
Note
"any type" really means any type. That includes Bool
, Int
, Text
, [Bool]
, [(Bool, Int)]
, functions like (Int -> Bool)
or (Int -> Int) -> Bool
, custom types you defined (e.g. ChessPiece
), Either Bool [Int]
, IO Int
, and so on.
Tip
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
.
In 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 a
and 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 x
, then 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, likeBool
,Either Bool Int
,[Bool]
and so on.- Consult this section if this is unclear. Note also that it will be displayed:
* -> * -> *
by the repl.
Note
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 *
¶
Tip
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