Functions
Source
John Whitington, OCaml from the Very Beginning, Chapter 9: More with Functions
Contents
Question 1
Rewrite the summary paragraph at the end of this chapter for the three argument function
g a b c
.
My solution
The function g a b c
has type α → β → γ → δ which can also be
written α → (β → (γ → δ)). Thus, it takes an argument of type α and
returns a function of type β → (γ → δ) which, when you give it an
argument of type β returns a function of type γ → δ which, when you
give it an argument of type γ returns something of type δ. And so,
we can apply just one argument to the function g
(which is called
partial application) or apply both at once. When we write let
g a
b c = ...
this is just shorthand for let
g =
fun
a ->
fun
b ->
fun
c -> ...
Solution provided in book
…is identical to my solution.
Question 2
Recall the function
member x l
which determines if an elementx
is contained in a listl
. What is its type? What is the type ofmember x
? Use partial application to write a functionmember_all x ls
which determines if an element is a member of all the lists in the list of listsls
.
My solution
The function member x l
has type α → β list → γ bool. The
type of member x
is α list → β bool.
I first wrote an all
function, which returns true if all of the items
in a provided list are evaluated true by a provided function.
1
2
3
4
let rec all f l = match l with
[h] -> f h
| [] -> false
| h::t -> f h && all f t
Then I wrote a simplified member
function (the example referred to by
the question was intended to verify if a key exists in a dictionary)
that operates on a list.
1
2
3
let rec member x l = match l with
[] -> false
| h::t -> if h = x then true else member x t
Finally, I defined member_all
by partially applying member
and
passing the result of that partial application, which is a function that
takes a list, as the argument to all
that represents the provided
function.
1
2
3
4
5
6
7
8
9
10
11
12
13
let member_all x ls = all (member x) ls
let _ = assert (false = member_all 0 [[]]);
assert (false = member_all 0 [[1]]);
assert (true = member_all 0 [[0]]);
assert (false = member_all 0 [[1]; [2]]);
assert (false = member_all 0 [[1; 2]; [2; 3]]);
assert (true = member_all 0 [[0]; [0]]);
assert (true = member_all 0 [[0; 1]; [0; 2]]);
;;
I have the sense there’s a more elegant solution to this that the book has in mind. We’ll see in a moment.
Solution provided in book
It turns out I wasn’t missing something. This solution is quite different,
using a map
function to build a list of boolean values, then checking
to see if that list contains false
.
Version 1:
1
2
3
4
5
6
let rec map f l =
match l with [] -> []
| h::t -> f h :: map f t
let member_all x ls =
let bools = map (member x) ls in not (member false bools)
Version 2:
1
2
let member_all x ls =
not (member false (map (member x) ls))
The book asks, “Which do you think is clearer?” (153). To me neither is as clear as my own solution.
Question 3
Why can we not write a function to halve all the elements of a list like this:
map (( / ) 2) [10; 20; 30]
? Write a suitable division function which can be partially applied in the manner we require.
My solution
Addition and multiplication are commutative: valid:
1
2
3
4
let _ =
assert ([2; 3; 4] = map (( + ) 1) [1; 2; 3]);
assert ([2; 4; 6] = map (( * ) 2) [1; 2; 3]);
;;
However, division and subtraction are not commutative. In map (( / ) 2)
[10; 20; 30]
, the partial application of the anonymous function ( / )
using the value 2
establishes that 2
is the dividend, rather than
the divisor. Therefore, it returns a function that expects an integer
value and treats that integer value as a divisor: that is, it divides
2
by that integer value. But that’s not what we want: we want 2
to
be treated as the divisor instead.
Here’s a division function that can be partially applied in the way we
expect, by specifying that its first argument is the divisor. When
partially applied with an integer value representing a divisor, mydiv
returns a function that expects an integer representing a dividend.
1
2
3
4
5
let mydiv divisor dividend = dividend / divisor
let _ = assert ([1; 2; 3] = map (mydiv 2) [2; 4; 6]);
assert ([1; 2; 3] = map (mydiv 3) [3; 6; 9]);
;;
Solution provided in book
…is identical to mine.
Question 4
Write a function
mapll
which maps a function over lists of lists of lists. You must not use the let rec construct. Is it possible to write a function which works likemap
,mapl
, ormapll
depending upon the list given to it?
My solution
1
2
3
4
5
6
7
let mapll f = map (map (map f))
let _ = assert (
[[[1; 2]; [3; 4]]; [[5; 6]; [7; 8]]] =
mapll (fun x -> x + 1) [[[0; 1]; [2; 3]]; [[4; 5]; [6; 7]]]
)
;;
With these constraints (using partial application, and avoiding the
let rec construct) it is not possible to write a function that works
like map
, mapl
, or mapll
depending upon the list given to it.
Solution provided in book
…is identical to my solution.
Question 5
Write a function
truncate
which takes an integer and a list of lists, and returns a list of lists, each of which has been truncated to the given length. If a list is shorter than the given length, it is unchanged. Make use of partial application.
My solution
Using the take
function defined in Chapter 4.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
let rec take n l =
if n = 0 then []
else match l with
[] -> []
| h::t -> h :: take (n - 1) t
let truncate x ll = map (take x) ll
let _ = assert ([] = take 0 []);
assert ([] = take 1 []);
assert ([] = take 0 [1; 2; 3]);
assert ([1] = take 1 [1; 2; 3]);
assert ([1; 2] = take 2 [1; 2; 3]);
assert ([1; 2; 3] = take 3 [1; 2; 3]);
assert ([1; 2; 3] = take 4 [1; 2; 3]);
;;
let _ = assert ([[]; []] = truncate 0 [[]; []]);
assert ([[]; []] = truncate 0 [[1; 2; 3]; [2; 3; 4]]);
assert ([[]; []] = truncate 0 [[]; [2; 3; 4]]);
assert ([[]; []] = truncate 0 [[1]; [2; 3; 4]]);
assert ([[1]; [2]] = truncate 1 [[1; 2; 3]; [2; 3; 4]]);
assert ([[1; 2; 3]; [2; 3; 4]] = truncate 3 [[1; 2; 3]; [2; 3; 4]]);
(* Lists of different lengths. *)
assert ([[1]; [2]] = truncate 1 [[1; 2; 3]; [2; 3; 4; 5; 6]]);
assert ([[1; 2]; [2; 3]] = truncate 2 [[1; 2; 3]; [2; 3; 4; 5; 6]]);
assert ([[1; 2; 3]; [2; 3; 4]] = truncate 3 [[1; 2; 3]; [2; 3; 4; 5; 6]]);
assert ([[1; 2; 3]; [2; 3; 4; 5]] = truncate 4 [[1; 2; 3]; [2; 3; 4; 5; 6]]);
(* Truncate to a length longer than either of the lists provided.
This should not fail. *)
assert ([[]; []] = truncate 3 [[]; []]);
assert ([[1; 2; 3]; [2; 3; 4]] = truncate 4 [[1; 2; 3]; [2; 3; 4]]);
assert ([[1; 2; 3]; [2; 3; 4]] = truncate 5 [[1; 2; 3]; [2; 3; 4]]);
assert ([[1; 2; 3]; [2; 3; 4; 5; 6]] = truncate 6 [[1; 2; 3]; [2; 3; 4; 5; 6]]);
;;
Solution provided in book
Uses the length
and take
functions defined previously, whereas my
solution uses only the take
function. I don’t see the need for this
solution. The book remarks on “being careful to deal with the case where
there is not enough [in the lists] to take” (153), but my simpler
solution handles such cases. The book provides a second solution using
exception handling to deal with such cases, but again, that is not
necessary: take
already handles such cases.
1
2
3
4
5
6
7
8
let rec length l =
match l with
[] -> 0
| _::t -> 1 + length t
let truncate_l n l = if length l >= n then take n l else l
let truncate n ll = map (truncate_l n) ll
Question 6
Write a function which takes a list of lists of integers and returns the list composed of all the first elements of the lists. If a list is empty, a given number should be used in place of its first element.
My solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let first n l = match l with [] -> n | h::_ -> h
let firsts n ll = map (first n) ll
let _ = assert ( 0 = first 0 [] );
assert ( 1 = first 0 [1] );
assert ( 1 = first 0 [1; 2] );
;;
let _ = assert ( [0] = firsts 0 [[]] );
assert ( [0; 0] = firsts 0 [[]; []] );
assert ( [0; 1] = firsts 0 [[]; [1]] );
assert ( [1; 0] = firsts 0 [[1]; []] );
assert ( [1; 1] = firsts 0 [[1]; [1]] );
assert ( [1; 0; 2] = firsts 0 [[1; 2]; []; [2; 3]] );
assert ( [1; 0; 2] = firsts 0 [[1; 2; 3]; []; [2; 3; 4; 5]] );
;;
Solution provided in book
…is identical to my solution.
Execute this file
$ codedown ocaml < 2023-11-14-functions.md | grep . | ocaml -stdin