H. Conrad Cunningham
7 November 2017
Acknowledgements: These slides accompany Chapter 8 “Algebraic Data Types” of the in-work textbook “Introduction to Functional Programming Using Haskell”.
Advisory: The HTML version of this document may require use of a browser that supports the display of MathML. A good choice as of November 2017 is a recent version of Firefox from Mozilla.
Code: The Haskell module for this chapter is in file Chap08.hs.
Introduce user-defined (algebraic) data types
Examine use of recursive algebraic data types
Explore safe error handling with Maybe and Either
Composite data type formed by combining other types
Careful! Acronym ADT sometimes used for two different structures
Algebraic Data Type specified with its syntax (structure) – rules on how to compose and decompose
Abstract Data Type specified with its semantics (meaning) – with rules on how operations behave in relation to each other
Data abstraction (e.g., Rational Arithmetic case study)
Algebraic data types formed by algebra of operations – recursive
Sum operation that constructs values with one variant among several – also tagged, disjoint union, or variant types
Using alternation operator – choice of exactly one of two alternatives
Product operation that combines several values (fields) together to construct single value – also tuple or record
Using Cartesian product operator
Haskell enables definition of new data types
data Datatype a1 a2 ... aN = Constr1 | Constr2 | ... | ConstrM
Datatype is (capitalized) name of new type constructor with arity N
a1 a2 ... aN are N distinct type variables for parameters
Constr1 Constr2 ... ConstrM are M distinct data constructors
Sum type whose constructors take no arguments (nullary)
data Color = Red | White | Blue
deriving (Show, Eq)
isRed :: Color -> Bool
isRed Red = True
isRed _ = False
deriving clause generates instance of classColor'data Color' = Red' | Blue' | Grayscale Int
deriving (Show, Eq)
Grayscale implicitly defines constructor function that takes argumentCan derive instances for Eq, Ord, Enum, Bounded, Read, Show using defaults based on declared structure
Eq includes (==) and (/=) methods – compare tags, values
Ord includes these plus compare, (<), (<=), (>), (>=), max, min methods – using declared order
Enum assigns integers to constructors increasing from 0 at left
Bounded assigns minBound to leftmost, maxBound to rightmost
Show enables function show to convert to syntactically correct expression string – with constructor names, parentheses, spaces
show (Grayscale 3) yields “Grayscale 3”
Read enables function read to parse above format into value
Booldata Bool = False | True -- as in Prelude
deriving (Ord, Show)
False < True yields Boolean True
False > True yields Boolean False
show x yields String False or String True
Pointdata Point a = Pt a a
deriving (Show, Eq)
2-D point with polymorphic type values
Both values associated with constructor Pt same type
Pt implicitly function of type a -> a -> Point a
Setdata Set a = Set [a]
deriving (Show, Eq)
makeSet :: Eq a => [a] -> Set a
makeSet xs = Set (nub xs) -- nub removes duplicates
Set used for both data type and constructor – different namespaces for types versus functions/variables
Avoid except when only one data constructor
type Matrix a = [[a]]
Allows type parameters like data
Does not introduce new type, just alias for existing type
data Result a = Ok a | Err String
deriving (Show, Eq)
divide :: Int -> Int -> Result Int
divide _ 0 = Err "Divide by zero"
divide x y = Ok (x `div` y)
-- use to handle error flexibly
f :: Int -> Int -> Int
f x y =
case divide x y of
Ok z -> z
Err s -> maxBound
case expression uses pattern match inside function
Maybe and Either types defined in Prelude
BinTree (1)data BinTree a = Empty | Node (BinTree a) a (BinTree a)
deriving (Show, Eq)
Binary tree with value in each node
“empty” (denoted by Empty)
“node” (denoted by Node) with value of type a and “left” and “right” subtrees
BinTree (2)Three-part “record” with nested records, no explicit pointers
Binary Tree BinTree
flattenflatten :: BinTree a -> [a]
flatten Empty = []
flatten (Node l v r) = flatten l ++ [v] ++ flatten r
flatten (Node (Node Empty 3 Empty) 5
(Node (Node Empty 7 Empty) 1 Empty))
== [3,5,7,1]
Under what circumstances does flatten t terminate normally?
What is time complexity of flatten t?
flattenflatten' :: BinTree a -> [a]
flatten' t = inorder t []
where inorder Empty xs = xs
inorder (Node l v r) xs =
inorder l (v : inorder r xs)
inorder builds list of values from right using cons
What is time complexity of flatten' t?
treeFoldReduce tree of values to value using associative operation
treeFold :: (a -> a -> a) -> a -> BinTree a -> a
treeFold op i Empty = i
treeFold op i (Node l v r) = op (op (treeFold op i l) v)
(treeFold op i r)
Treedata Tree a = Leaf a | Tree a :^: Tree a
deriving (Show, Eq)
Values only at leaves
Infix constructor :^:
Below: complete binary tree with height 3, integers 1, 2, 3, 4 at leaves
((Leaf 1 :^: Leaf 2) :^: (Leaf 3 :^: Leaf 4))
fringefringe :: Tree a -> [a]
fringe (Leaf v) = [v]
fringe (l :^: r) = fringe l ++ fringe r
fringe' :: Tree a -> [a]
fringe' t = leaves t []
where leaves (Leaf v) = ((:) v)
leaves (l :^: r) = leaves l . leaves r
leaves builds list of leaves from right using conslist of pairs in which first component is key, second is associated value
Dictionary, map, or table data structure
Example: associate student name with academic advisor name
lookup' :: String -> [(String,String)] -> String
lookup' key ((x,y):xys)
| key == x = y
| otherwise = lookup' key xys
What if key not in list (list empty)?
error call with custom error message?"NONE"?MaybeWhat is safer, more general approach?
data Maybe a -- type from Prelude
= Nothing -- no value
| Just a -- wraps value
lookup'' :: (Eq a) => a -> [(a,b)] -> Maybe b
lookup'' key [] = Nothing
lookup'' key ((x,y):xys)
| key == x = Just y
| otherwise = lookup'' key xys
Maybe (1)advisorList association list pairing students with their advisors
defaultAdvisor advisor student should consult if no advisor assigned
Example: whoIsAdvisor returns defaultAdvisor for Nothing
whoIsAdvisor :: String -> String
whoIsAdvisor std =
case lookup'' std advisorList of
Nothing -> defaultAdvisor
Just prof -> prof
Maybe (2)Data.Maybe library operations
fromMaybe :: a -> Maybe a -> a
isJust :: Maybe a -> Bool
isNothing :: Maybe a -> Bool
fromJust :: Maybe a -> a -- error if Nothing
Allow following rewrites of whoIsAdvisor
whoIsAdvisor' std =
fromMaybe defaultAdvisor $ lookup std advisorList
whoIsAdvisor'' std =
let ad = lookup std advisorList
in if isJust ad then fromJust ad else defaultAdvisor
EitherWhat if want specific error messages?
data Either a b -- from Prelude
= Left a -- convention: error msg
| Right b -- convention: value
deriving (Eq, Ord, Read, Show)
Available in Data.Either library
fromLeft :: a -> Either a b -> a
fromRight :: b -> Either a b -> b
isLeft :: Either a b -> Bool
isRight :: Either a b -> Bool
Java 8’s final class Optional<T>
Scala’s Option[T] case class hierarchy
Rust’s Option<T> enum (algebraic data type)
Idris, Elm, PureScript’s Haskell-like Maybe
Object-oriented languages Null Object design pattern
Algebraic data types
Recursive data types
Safe error-handling with Maybe and Either
The Haskell code for this section is in file Chap08.hs.