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
Bool
data 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
Point
data 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
Set
data 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
flatten
flatten :: 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
?
flatten
flatten' :: 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
?
treeFold
Reduce 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)
Tree
data 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))
fringe
fringe :: 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"
?Maybe
What 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
Either
What 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
.