Creating types
Products¶
You can make your own types like this:
- The choice of the names
Square
andSq
are both arbitrary. Both must be capitalized.
- This requires the GADT extension, which is included with GHC2021.
This creates a new type Square
; values of type Square
look like Sq i j
, where i
and j
are Int
s.
Sq
is referred to as a data constructor, and is a Haskell function, with type shown explicitly in the "modern" version above.
> :t Sq 5 4
Sq 5 4 :: Square
> :t Sq 5 True
"Couldn't match expected type ‘Int’ with actual type ‘Bool’"
> :t Sq
Sq :: Int -> (Int -> Square) -- (1)!
> :t Sq 3
Sq 3 :: Int -> Square -- (2)!
-
Actually, the repl will drop the brackets, and show:
Int -> Int -> Square
. -
If this is unclear, see here for more info.
Tip
The type Square
contains the same information as the product type (Int, Int)
. These types are different, in the sense that code which expects one will not work with the other, but it is easy to write loss-less functions between them:
Note
The number of types following Sq
can be 0 or more. For example:
If the number of types is 1
, you will see a suggestion to replace data
with newtype
. See more about newtype
here
Records¶
You can also name entries:
row
and col
are now accessing functions:
> entity = Sq 4 5
> :t entity
entity :: Entity
> row entity
4
> col entity
5
> :t row
row :: Entity -> Int
> :t col
col :: Entity -> Int
Sums¶
Entity
is a type, butSq
andPlayer
are values belonging to that type
The vertical bar |
indicates that an Entity
is either made with Sq
or with Piece
:
- This requires the GADT extension, which is included with GHC2021.
The newline indicates that an Entity
is either made with Sq
or with Player
:
> Sq 4 6
Sq 4 6 :: Entity
> :t Player False
Player False :: Entity
> :t Player
Player :: Bool -> Entity
Tip
The type Entity
contains the same information as the type Either (Int, Int) Bool
, and one can write loss-less functions between them:
Note
You can combine products and sums, using your own types:
data ChessPiece = Piece PieceType Color | SquareType Square -- (1)!
data Color = Black | White
data PieceType = Bishop | Rook
data Square = Sq Int Int
- Note that Haskell is unconcerned by the order of definitions: the definition of
Color
comes after its use in the definition ofChessPiece
.
Parameterized types¶
One can also create types which take another type as a parameter:
This creates Piece Bool
, Piece Int
, and so on:
> data Piece c = Bishop c | Knight c | King c
> :t Knight True
Knight True :: Piece Bool
let four = 4 :: Int
> :t King four
King four :: Piece Int
> :t King (King True)
King (King True) :: Piece (Piece Bool)
Note
One can think of Piece
as a function on types, which gives a type c
, produces a type Piece c
.
To make this idea explicit, one can say that Piece
has kind Type -> Type
, where kind is a name for the types that types themselves have.
Recursive types¶
Here, BinTree
is being used recursively in its own definition. Values of BinTree
include:
> data BinTree = Leaf Int | Branch BinTree BinTree
> :t Leaf 4
Leaf 4 :: BinTree
> :t Leaf True
> :t Branch (Leaf 4) (Leaf 5)
Branch (Leaf 4) (Leaf 5) :: BinTree
> :t Branch (Leaf 3) ((Branch (Leaf 6) (Leaf 8)))
Branch (Leaf 3) ((Branch (Leaf 6) (Leaf 8))) :: BinTree
Recursive types can also be parametrized:
data BinTree a = Leaf a | Branch (BinTree a) (BinTree a)
> :t Leaf True
Leaf True :: BinTree Bool
> :t Leaf ()
Leaf () :: BinTree ()
> :t Branch (Leaf True) (Leaf False)
Branch (Leaf True) (Leaf False) :: BinTree Bool
> :t Branch (Leaf True) (Leaf ())
"Couldn't match expected type ‘Bool’ with actual type ‘()’" -- (1)!
- The definition of
Branch
requires that the left and right branch be trees of the same type, which is why this doesn't work.
Here is a more complex recursive type and a program of that type:
data Machine a b = M (a -> (b, Machine a b))
machine :: Machine Int Int
machine = machine1 where
machine1 :: Machine Int Int
machine1 = M (\i -> (i, if i > 10 then machine2 else machine1))
machine2 :: Machine Int Int
machine2 = M (\i -> (0, machine2))
Note
The list type can be defined recursively in this way:
In fact, the [a]
type in Haskell is defined in this way, with the [1,2,3]
being extra syntax for convenience:
:
is the data constructor, analogous toHeadThenList
above, but written infix.[]
is analogous toEmptyList
.
Synonyms¶
One can also give new names to existing types:
Note
Here, Number
and Double
are not distinguished as separate types by the compiler, so replacing one by the other in a type signature will always be fine. This would not be true for:
This can be useful for readability, particularly for quite complex types:
Isomorphic types¶
Two types are isomorphic (or more colloquially, the same) if there are functions to convert between them in both directions that are loss-less:
data WrappedInt = MkW {getInt :: Int}
> :t MkW
MkW :: Int -> WrappedInt -- one direction
> :t getInt
getInt :: WrappedInt -> Int -- the other direction
> :t (getInt . MkW)
(getInt . MkW) :: Int -> Int
> (getInt . MkW) 4 -- (1)!
4
> :t (MkW . getInt)
(MkW . getInt) :: WrappedInt -> WrappedInt -- (2)!
(getInt . MkW)
is the identity function.(MkW . getInt)
is also the identity function.
Reference table of isomorphic types¶
A type | An isomorphic type |
---|---|
data WrappedInt = MkW Int |
Int |
data Wrapped a = MkW a |
a |
data Unit = U |
() |
data Pair = P Int Bool |
(Int, Bool) |
data OneOf = I Int | B Bool |
Either Int Bool |
a -> b -> c |
(a, b) -> c |
Bool -> a |
(a, a) |
Maybe a |
Either () a |
Reader e a |
e -> a |
State s a |
s -> (a, s) |
Except err a |
Either err a |
ReaderT e m a |
e -> m a |
StateT s m a |
s -> m (a, s) |
ExceptT err m a |
m (Either err a) |
Created: January 7, 2023