Conference: Week 5

Table Of Contents

1. Admistrativia

2. Type Inference

Infer the type for the following function use our type inference algorithm:

fun f(g,h) = g(h) + 2;

3. Folding Fun (Submit this one with your HW!)

Your GitLab account will have a “hw5” project for your to use for this question. You can follow the same instructions as on HW 1 for cloning it and 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.

  1. 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
  2. Using a fold operation, write a function words_length: string list -> int. This function should return the total length of all words appearing in a list of strings. For example:

    - words_length nil;
    val it = 0 : int
    - words_length ["Three", "Short", "Words"];
    val it = 15 : int
  1. 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.

  2. 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
  3. 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
  4. Using a fold, write a function poly: real list -> (real -> real) that takes a list of reals [a_0, a_1, ..., a_{n-1}] and returns a function that takes an argument b and evaluates the polynomial \[a_0 + a_1 x + a_2 x^2 + \cdots + a_{n-1} x^{n-1}\] 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.)