H. Conrad Cunningham
24 October 2018
Copyright (C) 2017, 2018, H. Conrad Cunningham
Acknowledgements: I originally created these slides in Fall 2017 to accompany what is now Chapter 21, Algebraic Data Types, in the 2018 version of the textbook Exploring Languages with Interpreters and Functional Programming.
Browser Advisory: The HTML version of this document may require use of a browser that supports the display of MathML. A good choice as of October 2018 is a recent version of Firefox from Mozilla.
Source Code: The Haskell module for this chapter is in file AlgDataTypes.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) — rules on how operations behave in relation to each other
Data abstraction (e.g. Chapter 7, Rational Arithmetic)
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
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 _   = Falsederiving clause generates instances of classesColor'Grayscale implicitly defines constructor function that takes argument – productCan derive instances for classes Eq, Ord, Enum, Bounded, Read, Show using defaults based on declaration
Eq includes (==), (/=) methods — compare tags and values
Ord adds compare, (<), (<=), (>), (>=), max, min methods — using declared order
Enum assigns integers increasing from 0 at left
Bounded assigns minBound to left, maxBound to right
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
BoolFalse < True yields Boolean True
False > True yields Boolean False
show x yields string "False" or "True"
Point2-D point with polymorphic type values — product
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 duplicatesSet used for both data type and constructor — different namespaces for types versus functions/variables
Avoid except when only one data constructor
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 -> maxBoundcase expression uses pattern match inside function
Maybe and Either types defined in Prelude
BinTree (1)Binary tree with value in each node – recursive
“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
flattenUnder 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
TreeValues only at leaves
Infix constructor :^:
Below: complete binary tree with height 3, integers 1, 2, 3, 4 at leaves
fringefringe' :: Tree a -> [a] 
fringe' t = leaves t [] 
    where leaves (Leaf v)  = ((:) v) 
          leaves (l :^: r) = leaves l . leaves rleaves 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
What if key not in list (list empty)?
error call with custom error message?"NONE"?MaybeWhat is safer, more general approach?
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
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
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
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 module for this chapter is in file AlgDataTypes.hs.