CSci 450/503: Programming Languages

Algebraic Data Types

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.

Lecture Goals

Definition

 Algebraic Data Type:

Composite data type formed by combining other types

Construction

Algebraic data types formed by algebra of operations – recursive

  1. 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

  2. Product operation that combines several values (fields) together to construct single value – also tuple or record

    Using Cartesian product operator

Declaration

Haskell enables definition of new data types

data Datatype a1 a2 ... aN = Constr1 | Constr2 | ... | ConstrM

Enumerated Types

 Enumerated type

Sum type whose constructors take no arguments (nullary)

data Color = Red | White | Blue
deriving (Show, Eq)
isRed :: Color -> Bool
isRed Red = True
isRed _ = False

Example Type Color'

data Color' = Red' | Blue' | Grayscale Int
deriving (Show, Eq)

Deriving Class Instances (1)

Deriving Class Instances (2)

Type Bool

data Bool = False | True -- as in Prelude
deriving (Ord, Show)

Example Type Point

data Point a = Pt a a
deriving (Show, Eq)

Example Type Set

data Set a = Set [a]
deriving (Show, Eq)
makeSet :: Eq a => [a] -> Set a
makeSet xs = Set (nub xs) -- nub removes duplicates

Type Synonyms

type Matrix a = [[a]]

Types to Encode Errors

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

Recursive Types

Type BinTree (1)

data BinTree a = Empty | Node (BinTree a) a (BinTree a)
deriving (Show, Eq)

Type BinTree (2)

Three-part “record” with nested records, no explicit pointers

Binary Tree BinTree

Binary Tree BinTree

Function 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]

Tail Recursive 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)

Function 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)

Type Tree

data Tree a = Leaf a | Tree a :^: Tree a
deriving (Show, Eq)
((Leaf 1 :^: Leaf 2) :^: (Leaf 3 :^: Leaf 4))

Function 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

Error Handling

Motivation (1)

Association list:

list of pairs in which first component is key, second is associated value

lookup' :: String -> [(String,String)] -> String
lookup' key ((x,y):xys)
| key == x = y
| otherwise = lookup' key xys

Motivation (2)

What if key not in list (list empty)?

  1. Leave undefined for empty list?
  2. Insert error call with custom error message?
  3. Return some default value for advisor, e.g. "NONE"?
  4. Return null reference?

Type 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

Using Maybe (1)

whoIsAdvisor :: String -> String
whoIsAdvisor std =
case lookup'' std advisorList of
Nothing -> defaultAdvisor
Just prof -> prof

Using 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

Type 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

Other Languages

Key Ideas

Code

The Haskell code for this section is in file Chap08.hs.