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.
Your GitLab account will have a "lab5" project. You can follow the same instructions as on Lab 1 for cloning it and (optionally) adding a partner.
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;
Thus, foldr g b [a_0, ..., a_n] computes
g(a_0, g(a_1, g(a_2, ... g(a_{n}, b) ... )))
and foldl g b [a_0, ..., a_n] computes
g(a_{n}, g(a_{n-1}, g(a_{n-2}, ..., g(a_{0}, b) ... )))
The "fold-right" function reduces the elements in a list to a single value by repeated application of g, starting at the right of the list and working to the left. The "fold-left" function starts from the left and works to the right.
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.