Algebraic Datatypes in Haskell
CS 430 :: Bowers :: Spring ‘19
An algebraic data type is a type formed by combining other types.
- Sort of like a
structin C, only better because it enables pattern matching. - Defines what values the type can take:
data Bool = False | True
data Int = -2147483648 | -2147483647 | ... | -1 |
0 | 1 | 2 | ... | 2147483647
data Boolish = False | True | OutcomeInDoubt
data Year = Year Int
data Maybe a = Just a | Empty
tenYearsFromNow :: Year -> Year
tenYearsFromNow (Year x) = (Year (x + 10))
twentyYearsFrom y = tenYearsFromNow $ tenYearsFromNow y
tyfnAsInt :: Year -> Int
tyfnAsInt (Year x) = (x + 10)
| (Think of | as or.) |
Think of a type as a set of values (forget the operations for now).
i.e. $\mathbb{Z}$ is the integers.
We can take products of sets:
- $\mathbb{Z} \times \mathbb{R}$ is all ordered pairs $(x, y)$ where $x \in \mathbb{Z}$ and $y\in \mathbb{R}$.
We can take unions of sets:
- ${1, 2, 3} \cup {2, 3, 4} = {1, 2, 3, 4}$.
- Sometimes written ${1, 2, 3} + {2, 3, 4} = {1, 2, 3, 4}$
So we have operations $\times$ and $+$ on sets (looks like algebra), and this in turn is easily extended to types.
Products of Types
data Point = Point Float Float
If $Float$ is the set of all floats, then Point Float Float is like saying $Point = Float \times Float$.
We can now create a point like Point 3.4 4.7.
The word Point is called a value constructor and the Point Float Float above says that a Point holds two Float values.
To create a point, you just specify the value constructor and two floats:
Point 3.0 4.8
Similarly, we can sum types.
data Point -- A point is either 2D or 3D
= Point2D Float Float
| Point3D Float Float Float
data Point = Point2D Float Float
| Point3D Float Float Float
data Segment = Segment (Point, Point)
Algebraic datatypes are especially powerful when mixed with pattern matching:
xCoord :: Point -> Float
xCoord (Point2D x _) = x
xCoord (Point3D x _ _) = x
yCoord :: Point -> Float
yCoord (Point2D _ y) = y
yCoord (Point3D _ y _) = y
zCoord :: Point -> Float
zCoord (Point3D _ _ z) = z
start (Segment (p1, _)) = p1
end (Segment (_, p2)) = p2
xCoord $ start (Segment ((Point2D 3 4), (Point2D 5 6)))
Consider the following C style struct for a tree and suppose we only want to store data at the leaf nodes:
typedef struct node {
struct node *left;
struct node *right;
int value; // Storing values at internal nodes also
} node;
bool isLeaf(struct node *n) {
// Wouldn't it be nice if leaf nodes looked different
// than internal nodes?
return (*n).left == NULL && (*n).right == NULL;
}
Haskell version:
data Tree
= Empty
| Leaf Int
| Node Tree Tree
Read this as: “a Tree is either the Empty tree, or is a Leaf node storing a value, or is a Node with two children, both of which are Tree typed.”
Notice that the definition of Tree uses Tree within the definition. This is a recursive type.
Haskell version:
data Tree
= Empty
| Leaf Int
| Node Tree Tree
How would you code for the following tree?
.
/ \
. 3
/ \
4 5
Haskell version:
data Tree
= Empty
| Leaf Int
| Node Tree Tree
How would you code for the following tree?
.
/ \
. 3
/ \
4 5
Node (Node (Leaf 4) (Leaf 5)) (Leaf 3)
data Tree
= Empty
| Leaf Int
| Node Tree Tree
Allows for some great pattern matching:
allValues :: Tree -> [Int]
allValues t = case t of
Empty -> []
(Leaf x) -> [x]
(Node l r) -> (allValues l) ++ (allValues r)
> allValues (Node (Node (Leaf 4) (Leaf 5)) (Leaf 3))
data Tree
= Empty
| Leaf Int
| Node Tree Tree
Something interesting:
*Main> :t Node
Node :: Tree -> Tree -> Tree
*Main> :t Leaf
Leaf :: Int -> Tree
So even value constructors are really functions!
In GHCI we’d like to print out the value of a tree, like:
> (Node (Leaf 2) (Leaf 3))
but this will give us an error. Haskell only prints things that are part of the type class Show. Fortunately, we can get this almost automagically:
data Tree
= Empty
| Leaf Int
| Node Tree Tree
deriving (Show)
This is sort of like implements Show in Java. Just like algebraic types are sort of like structs, type classes are sort of like interfaces. Now our Tree is “showable”.
Other typeclasses you might want: Eq (can test equality), Ord (can order / compare), Show (can convert to a string), Read (can read from a string).
data List a
= Empty
| Prepend a (List a)
deriving (Show, Read, Eq, Ord)
3 `Prepend` (4 `Prepend` (5 `Prepend` Empty))
-- same as
Prepend 3 (Prepend 4 (Prepend 5 Empty))
-- compare to
3 : (4 : (5 : [])) -- i.e. [3, 4, 5]
-- same as
(:) 3 ((:) 4 ((:) 5 []))
listHead (Prepend x _) = x
listTail (_ `Prepend` t) = t
listLength Empty = 0
listLength (x `Prepend` xs) = 1 + (listLength xs)
data List a
= Empty
| Cons a (List a)
deriving (Show, Read, Eq, Ord)
3 `Cons` (4 `Cons` (5 `Cons` Empty))
-- compare to
3 : (4 : (5 : [])) -- i.e. [3, 4, 5]
listHead (x `Cons` _) = x
-- head (x:_) = x
listTail (_ `Cons` t) = t
-- tail (_:t) = t
listLength Empty = 0
-- length [] = 0
listLength (x `Cons` xs) = 1 + (listLength xs)
--length (x:xs) = 1 + (length xs)