CSci 450/503: Programming Languages

Higher-Order List Programming

H. Conrad Cunningham

5 October 2017

Acknowledgements: These slides accompany Section 5.2 “Higher-Order List Functions” from Chapter 5 “Higher-Order Functions” from 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 October 2017 is a recent version of Firefox from Mozilla.

Code: The Haskell module for this chapter is in file Mod05HigherOrder.hs.

Higher-Order List Programming

Lecture Goals

Procedural Abstraction

Procedural abstraction.

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)

Study Similar Functions

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

Data Transformations

map is recursive list function, but we can consider it:

  1. powerful list operator transforming elements simultaneously

    Parallelism “easy” given referential transparency and immutable data. E.g. consider Google’s mapReduce “big data” framework

  2. operator node in dataflow network

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

Method for Generalizing Functions (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 Functions (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)

Grouping from Left

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)

HOF 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 []

Conclusion

Key Ideas

Code

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