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
-
Leftis a function which takesTrueas an argument. In other languages, this might be writtenLeft(True) -
Rightis a function which takes3as 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)!
-
Justis a function of typeBool -> Maybe Bool. -
The most general type of
Nothingisforall 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, but at an introductory level is fairly rare.
The list type¶
The type of a list of Bools 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 with 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