In this lab, you will explore the fold higher-order functions
in ML and use them to write a variety of list-processing
functions concisely.
You may work with a partner on this lab if you like, but it is not required.
You will receive an email with an invitation link to the lab5
assignment on GitHub Classroom. You can follow the
same instructions as on Lab 2 for accessing and
cloning your repository. See the GitHub reference for instructions to add
a partner. You should answer the following in
the *.sml files in your repository.
For this lab, use the ML interpreter on the Unix machines in the computer lab, or on your own computer. See instructions here.
The "fold-left" (and "fold-right") functions appear in many
languages (as reduceRight/Left in Javascript, as accumulate in
C++, as foldl/foldr in ML, and so on.)
Here are their definitions in ML:
fun foldr f v nil = v
| foldr f v (x::xs) = f (x, foldr f v xs);
fun foldl f v nil = v
| foldl f v (x::xs) = foldl f (f(x, v)) xs;
foldr (fold-right) builds up computation from the right side of the list.
foldr f v [a_0, ..., a_n] computes f(a_0, f(a_1, f(a_2, ... f(a_{n}, v) ... ))):
foldr f v [1, 2, 3]
f
/ \
1 f
/ \
2 f
/ \
3 v
Computes: f(1, f(2, f(3, v)))
foldl (fold-left) builds up computation from the left side.
foldl f v [a_0, ..., a_n] computes f(a_{n}, f(a_{n-1}, f(a_{n-2}, ..., f(a_{0}, v) ... ))):
foldl f v [1, 2, 3]
f
/ \
3 f
/ \
2 f
/ \
1 v
Computes: f(3, f(2, f(1, v)))
Here is an example usage, which defines a function sum that adds
together the numbers in a list:
- fun add(x,y) = x+y;
- fun sum elems = foldr add 0 elems;
- sum [2,3,4];
val it = 9: int
In effect, sum [2,3,4] computes
add(2, add(3, add(4, 0)))
Writing that function recursively would give us:
fun sum_rec nil = 0
| sum_rec (x::xs) = x + sum_rec(xs);
which computes the exact same value: sum_rec [2,3,4] computes 2
+ (3 + (4 + 0)). Many computations involve traversing a list and
computing a "summary" value for it. We explore other examples below,
and our folding operations enable us to write them in a succinct,
elegant way.
We could also define sum using foldl:
- fun sum2 elems = foldl add 0 elems;
in which case sum2 [2,3,4] computes
add(4, add(3, add(2, 0)))
Of course, we typically combine folding with anonymous functions, as
in the following definition of sum:
- fun sum elems = foldr (fn (x,result) => (x+result)) 0 elems;
The type of both foldr and foldl is
('c * 'd -> 'd) -> 'd -> 'c list -> 'd
That is, it takes as parameters a reducing function, an initial value, and a list. It produces a single summary value.
Using a fold operation, write a function
concatWords: string list -> string. This function should
return return a string with all strings in the list
concatenated:
- concatWords nil;
val it = "" : string
- concatWords ["Three", "Short", "Words"];
val it = "ThreeShortWords" : string
Using a fold operation, write a function
wordsLength: string list -> int. This function should return
the total length of all words appearing in a list of strings.
For example:
- wordsLength nil;
val it = 0 : int
- wordsLength ["Three", "Short", "Words"];
val it = 15 : int
Can we always use foldl in place of foldr? If yes, explain.
If no, give an example function f, list l, and initial value
v such that foldr f v l and foldl f v l behave
differently.
Using a fold, write a function count: ''a -> ''a list -> int. It
computes the number of times a value appears in a list. For
example:
- count "sheep" ["cow", "sheep", "sheep", "goat"];
val it = 2 : int
- count 4 [1,2,3,4,1,2,3,4,1,2,3,4];
val it = 3 : int
Using a fold, write a function
partition: int -> int list -> int list * int list that takes
an integer p and a list of integers l, and that returns a
pair of lists containing the elements of l smaller than p
and those greater than or equal to p. The ordering of the
original list should be preserved in the returned lists. (We
wrote a recursive form during lecture as part of quicksort.)
- partition 10 [1,4,55,2,44,55,22,1,3,3];
val it = ([1,4,2,1,3,3],[55,44,55,22]) : int list * int list
Using a fold, write a function
poly: real list -> (real -> real) that takes a list of reals
c[a_0, a_1, ..., a_{n-1}] and returns a function that takes an
argument b and evaluates the polynomial
at x = b; that is, it computes \sum_{i=0}^{n-1} a_i b^i. For example,
- val g = poly [1.0, 2.0];
val it = fn: real -> real
- g(3.0);
val it = 7.0: real
- val g = poly [1.0, 2.0, 3.0];
val it = fn: real -> real
- g(2.0);
val it = 17.0: real
Hint
a_0 + a_1 x + a_2 x^2 + a_3 x^3 = a_0 + x (a_1 + x (a_2 + x a_3)). This is an example of Horner's Rule. Horner's Rule demonstrates that we can evaluate a degree n polynomial with only O(n) multiplies.
Submit your code to the GradeScope assignment named, for example, "Lab 1". You can submit in one of two ways:
Please do not change the names of the starter files. Also:
Autograding: Gradescope will run an autograder on your code that performs some simple tests. Be sure to look at the autograder output to verify your code works as expected. We will run more extensive tests on your code after the deadline.