Exploring Languages
with Interpreters
and Functional Programming

Chapter 15
Higher Order Functions

H. Conrad Cunningham

2 October 2018

Copyright (C) 2017, 2018, H. Conrad Cunningham

Acknowledgements: I originally created these slides in Fall 2017 to accompany what is now Chapter 15, Higher Order Functions, 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.

Higher-Order Functions

Lecture Goals

Procedural Abstraction

Procedural abstraction: (Ch. 2)
separates logical properties of computation from details of implementation

Generalizing Procedural Abstractions

First Class and Higher Order

First-class value:

can be stored in a data structure, passed as argument, returned as result

Higher-order function (HOF):

takes functions as arguments or returns function as result

Haskell functions are first class values, can be higher-order

Advantages of HOFs

Haskell higher-order functions (HOF)

\Longrightarrow construct powerful abstractions and operations

\Longrightarrow express concise, “easy-to-understand” programs

\Longrightarrow “glue together” programs from library functions and new program fragments

\Longrightarrow improve programmer productivity and program reliability

Defining Map (map)

    squareAll :: [Int] -> [Int] 
    squareAll []     = [] 
    squareAll (x:xs) = (x * x) : squareAll xs

    lengthAll :: [[a]] -> [Int] 
    lengthAll []       = [] 
    lengthAll (xs:xss) = (length xs) : lengthAll xss

Generalize to HOF map

    map' :: (a -> b) -> [a] -> [b] -- map in Prelude
    map' f []     = [] 
    map' f (x:xs) = f x : map' f xs 

Specialize map

    squareAll2 :: [Int] -> [Int]
    squareAll2 xs = map' sq xs 
                    where sq x = x * x 

    lengthAll2 :: [[a]] -> [Int] 
    lengthAll2 xss = map' length xss

Analyze map

    map' :: (a -> b) -> [a] -> [b] -- map in Prelude
    map' f []     = [] 
    map' f (x:xs) = f x : map' f xs 

Analyze Specializations

    squareAll2 :: [Int] -> [Int]
     squareAll2 xs = map' sq xs 
                    where sq x = x * x 

    lengthAll2 :: [[a]] -> [Int] 
    lengthAll2 xss = map' length xss

Think Like Functional Programmer

map is recursive list function, but can consider it:

  1. powerful list operator transforming elements simultaneously

    Parallelism “easy” given referential transparency & immutable data—consider Google’s mapReduce

  2. operator node in dataflow network

    Demand-driven network enabled by lazy evaluation  stream \longrightarrow [map node] \longrightarrow transformed-stream

Method for Generalizing (1)

  1. Do scope-commonality-variability analysis (SCV) on set of functions

    • scope: what is and is not included

    • commonalities: parts of functions that are same (frozen spots)

    • variabilities: parts of functions that differ (hot spots)

  2. Leave commonalities in function body.

  3. Move variabilities into type signature & parameter list

Method for Generalizing (2)

  1. Move variabilities into type signature & parameter list

    • Make expression moved a function with parameter for each local variable accessed

    • Add type parameter to signature for types that differ among functions in scope

    • Add distinct type or value parameter for each potential role that type/value plays among functions in scope

  2. Consider other approaches if many new parameters (e.g., new procedural or data abstractions)

Defining Filter (filter)

Study similar functions

    getEven :: [Int] -> [Int] 
    getEven []      = [] 
    getEven (x:xs)
        | even x    = x : getEven xs 
        | otherwise = getEven xs

    doublePos :: [Int] -> [Int] 
    doublePos []          = [] 
    doublePos (x:xs) 
        | 0 < x     = (2 * x) : doublePos xs 
        | otherwise = doublePos xs

Identify Commonalities

Identify Variabilities

Generalize to HOF filter

    filter' :: (a -> Bool) -> [a] -> [a]  -- filter in Prelude
    filter' _ []    = [] 
    filter' p (x:xs)
        | p x       = x : xs' 
        | otherwise = xs' 
                      where xs' = filter' p xs

Specialize filter

    getEven2 :: [Int] -> [Int] 
    getEven2 xs = filter' even xs 

    doublePos2 :: [Int] -> [Int] 
    doublePos2 xs = map' dbl (filter' pos xs) 
                    where dbl x  = 2 * x
                          pos x = (0 < x)

Analyze filter

    filter' :: (a -> Bool) -> [a] -> [a]  -- filter in Prelude
    filter' _ []    = [] 
    filter' p (x:xs)
        | p x       = x : xs' 
        | otherwise = xs' 
                      where xs' = filter' p xs

Analyze Specializations

    getEven2 :: [Int] -> [Int] 
    getEven2 xs = filter' even xs 

    doublePos2 :: [Int] -> [Int] 
    doublePos2 xs = map' dbl (filter' pos xs) 
                    where dbl x  = 2 * x
                          pos x = (0 < x)

Defining Fold Right (foldr)

Study similar functions

    sum' :: [Int] -> Int             -- sum in Prelude
    sum' []     = 0
    sum' (x:xs) = x + sum' xs 
        
    product' :: [Integer] -> Integer -- product in Prelude
    product' []     = 1 
    product' (x:xs) = x * product' xs 

    concat' :: [[a]] -> [a]          -- concat in Prelude
    concat' []       =  []
    concat' (xs:xss) =  xs ++ concat' xss
    sum' [1,2,3]          = (1 + (2 + (3 + 0)))
    product' [1,2,3]      = (1 * (2 * (3 * 1)))
    concat' ["1","2","3"] = ("1" ++ ("2" ++ ("3" ++ "")))

Identify Commonalities

Identify Variabilities

Exploring Generalization

Generalize to HOF foldr (1)

    foldrX :: (a -> b -> b) -> b -> [a] -> b  -- foldr in Prelude
    foldrX f z []     = z
    foldrX f z (x:xs) = f x (foldrX f z xs) 

Generalize to HOF foldr (2)

    foldrX :: (a -> b -> b) -> b -> [a] -> b  -- foldr
    foldrX f z []     = z
    foldrX f z (x:xs) = f x (foldrX f z xs) 

foldr f z [1,2,3] expands to

    f 1 (f 2 (f 3 z))       -- prefix form
    1 `f` (2 `f` (3 `f` z)) -- infix form

Specialize foldr

    sum2 :: [Int] -> Int             -- sum
    sum2 xs = foldrX (+) 0 xs

    product2 :: [Int] -> Int         -- product
    product2 xs = foldrX (*) 1 xs

    concat2:: [[a]] -> [a]           -- concat
    concat2 xss = foldrX (++) [] xss

    and', or' :: [Bool] -> Bool      -- and, or
    and' xs = foldrX (&&) True xs 
    or'  xs = foldrX (||) False xs

Analyze foldr

    foldrX :: (a -> b -> b) -> b -> [a] -> b  -- foldr
    foldrX f z []     = z
    foldrX f z (x:xs) = f x (foldrX f z xs) 

Analyze Specializations

    product2 :: [Int] -> Int         -- product
    product2 xs = foldrX (*) 1 xs

    concat2:: [[a]] -> [a]           -- concat
    concat2 xss = foldrX (++) [] xss

Using foldr for map

    map2 :: (a -> b) -> [a] -> [b] -- map
    map2 f xs = foldr mf [] xs 
        where mf y ys = (f y) : ys

Using foldr for filter

    filter2 :: (a -> Bool) -> [a] -> [a]  -- filter
    filter2 p xs = foldr ff [] xs
        where ff y ys = if p y then (y:ys) else ys

Using foldr for length

    length2 :: [a] -> Int  -- length
    length2 xs  = foldr len  0  xs
        where len _ acc = acc + 1

Using foldr for append

    append2 :: [a] -> [a] -> [a]  -- ++
    append2 xs ys = foldr (:) ys xs

Think Like Functional Programmer

Defining Fold Left (foldl)

    foldr :: (a -> b -> b) -> b -> [a] -> b

    foldr f z [1,2,3] ==  f 1 (f 2 (f 3 z))
                      ==  1 `f` (2 `f` (3 `f` z))

What about same functionality except group from left?

    foldl f z [1,2,3]  ==  f (f (f z 1) 2) 3
                       ==  ((z `f` 1) `f` 2) `f` 3`

HOF foldl

    foldlX :: (a -> b -> a) -> a -> [b] -> a  -- foldl
    foldlX f z []     = z
    foldlX f z (x:xs) = foldlX f (f z x) xs

First Duality Theorem

If \oplus is an associative binary operation of type t -> t -> t with identity element z then, for any finite xs.

foldr ( \oplus) z xs = foldl ( \oplus) z xs

Informal Look at Strictness

Rule of Thumb

  1. If operation \oplus nonstrict in either argument, then often better to use foldr—exploits lazy evaluation

  2. If operations \oplus strict in both arguments, then more efficient to use foldl' from library Data.List

Choosing foldr or foldl (1)

    import Data.List   -- to make foldl' available
    sum3, product3 :: Num a =>  [a] -> a -- sum, product
    sum3 xs     = foldl' (+) 0 xs
    product3 xs = foldl' (*) 1 xs

Choosing foldr or foldl (2)

    length3 :: [a] -> Int  -- length
    length3 xs  = foldl len 0  xs
        where len acc _ = acc + 1

Choosing foldr or foldl (3)

    reverse2 :: [a] -> [a]  -- reverse
    reverse2 xs = foldl rev [] xs
        where rev acc x = (x:acc) 

Using foldl for foldr

    foldr2 :: (a -> b -> b) -> b -> [a] -> b  -- foldr
    foldr2 f z xs = foldl flipf z (reverse xs)
        where flipf y x = f x y

Defining Flat Map (concatMap)

    concatMap' :: (a -> [b]) -> [a] -> [b] 
    concatMap' f xs = concat (map f xs)
    concatMap2 :: (a -> [b]) -> [a] -> [b] 
    concatMap2 f xs = foldr fmf [] xs 
        where fmf x ys = f x ++ ys

Using concatMap for filter

    filter3 :: (a -> Bool) -> [a] -> [a]
    filter3 p xs = concatMap' fmf xs
        where fmf x = if p x then [x] else []

Key Ideas

Code

The Haskell code module for this section is in file HigherOrderFunctions.hs