Exploring Languages
with Interpreters
and Functional Programming

Chapter 21
Algebraic Data Types

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.

Algebraic Data Types

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

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

Error-Handling 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

Source Code

The Haskell module for this chapter is in file AlgDataTypes.hs.