5.4 Tail recursion
5.4.2 Lists and tail recursion
Tail-recursion is especially important when programming with lists, because otherwise functions would usually take stack space linear in the length of the list. Not only would that be slow, but it would also mean that the list length is limited by the maximum stack size.
Fortunately, there are some standard techniques for writing tail-recursive functions. If the function is to return a list, one standard approach is to use an accumulator that collects the result in reverse order. For example, consider the following implementation of a functionmap.
let recmap f = function h :: t -> f h :: map f t | [] -> []
The function definition is simple, but it is not tail-recursive. To obtain a tail recursive version, we collect the result in an argumentaccum.
let recrev accum = function h :: t -> rev (h :: accum) t | [] -> accum
let recrev_map f accum = function h :: t -> rev_map f (f h :: accum) t | [] -> accum
let map f l = rev [] (rev_map f [] l)
Note that the result is collected inaccuminreverseorder, so it must be reversed (with
the functionrev) at the end of the computation. Still, traversing the list twice, once with
5.4. TAIL RECURSION CHAPTER 5. TUPLES, LISTS, AND POLYMORPHISM For clarity, we listed the revfunction here. Normally, one would use the standard
CHAPTER 5. TUPLES, LISTS, AND POLYMORPHISM 5.5. EXERCISES
5.5 Exercises
Exercise 5.1 The comma,that is used to separate the elements of a tuple has one of
the lowest precedences in the language. How many elements do the following tuples have, and what do the expressions evaluate to?
1. 1 + 2, 3, - 5
2. "ABC", ( 1 , "def" ), ()
3. let x = 1 in x + 1, let y = 2 in y + 1, 4
Exercise 5.2 What are the types of the following functions? 1. let f (x, y, z, w) = x + z
2. let f (x, y, z, w) = (w, z, y, x)
3. let f [x; y; z; w] = x
4. let f [x; y] [z; w] = [x; z]
5. let f (x, y) (z, w) = [x; z]
Exercise 5.3 One of the issues with tuples is that there is no general destructor function that takes a tuple and projects an element of it. Suppose we try to write one for triples.
let nth i (x, y, z) = matchi with 1 -> x | 2 -> y | 3 -> z | _ -> raise(Invalid_argument "nth")
1. What is the type of thenthfunction?
2. Is there a way to rewrite the function so that it allows the elements of the tuple to have different types?
Exercise 5.4 Suppose you are implementing a relational employee database, where the database is a list of tuplesname * phone * salary.
let db =
["John", "x3456", 50.1; "Jane", "x1234", 107.3; "Joan", "unlisted", 12.7]
1. Write a functionfind_salary : string -> floatthat returns the salary of an
employee, given the name. 2. Write a general function
5.5. EXERCISES CHAPTER 5. TUPLES, LISTS, AND POLYMORPHISM select : (string * string * float -> bool) -> (string * string * float) list that returns a list of all the tuples that match the predicate. For example the ex- pressionselect (fun (_, _, salary) -> salary < 100.0)would return the tuples for John and Joan.
Exercise 5.5 We have seen that the identity function(fun x -> x)has type’a -> ’a. Are there any other functions with type’a -> ’a?
Exercise 5.6 In Exercise 3.7 we saw that partial application is sometimes used to im- prove performance of a functionf(x, y)under the following conditions:
• the function can be written in the formf(x, y) =h(g(x), y), and • f is to be called for multiple values ofywithxfixed.
In this case, we codef(x, y)as follows, so thatg(x)is computed whenf is partially applied to its first argument.
let f x = h (g x)
Unfortunately, this technique doesn’t always work in the presence of polymorphism. Suppose the original type of the function isf : int -> ’a -> ’a, and we want to
compute the values of(f 0 "abc")and(f 0 1.2). let f’ = f 0
let v1 = f’ "abc" let v2 = f’ 1.2
What goes wrong? How can you compute both values without computingg 0twice?
Exercise 5.7 The functionappend : ’a list -> ’a list -> ’a listappends two
lists. It can be defined as follows. let rec append l1 l2 =
match l1 with
h :: t -> h :: append t l2 | [] -> l2
Write a tail-recursive version ofappend.
Exercise 5.8 It is known that a welfare crook lives in Los Angeles. You are given lists for 1) people receiving welfare, 2) Hollywood actors, and 3) residents of Beverly Hills. The names in each list are sorted alphabetically (by<). A welfare crook is someone who appears in all three lists. Write an algorithm to find at least one crook.
Chapter 6
Unions
Disjoint unions, also calledtagged unions, variant records, oralgebraic data types, are an important part of the OCaml type system. A disjoint union, or union for short, represents the union of several different types, where each of the parts is given an unique, explicit name.
OCaml allows the definition ofexactandopenunion types. The following syntax is used for an exact union type; we discuss open types later in Section 6.5.
typetypename = | Identifier1 of type1 | Identifier2 of type2 . . . | Identifiern of typen
The union type is defined by a set of cases separated by the vertical bar|character; the first vertical bar is optional. Each caseihas an explicit nameIdentifieri, called a
constructor name; and it has an optional value of typetypei. The constructor name
must be capitalized. The definitionof typeiis optional; if omitted there is no explicit
value associated with the constructor.
Let’s look at a simple example using unions, where we wish to define a numeric type that is either a value of typeintorfloator a canonical valueZero. We can define
this type as follows. # type number =
Zero
| Integerof int | Real of float;;
type number = Zero | Integer of int | Real of float
Values in a disjoint union are formed by applying a constructor to an expression of the appropriate type.
# letzero = Zero;;
val zero : number = Zero
# leti = Integer 1;;
CHAPTER 6. UNIONS # let x = Real 3.2;;
val x : number = Real 3.2
Patterns also use the constructor name. For example, we can define a function that returns a floating-point representation of a number as follows. In this program, each pattern specifies a constructor name as well as a variable for the constructors that have values.
# let float_of_number = function Zero -> 0.0
| Integer i -> float_of_int i | Real x -> x
Patterns can be arbitrarily nested. The following function represents one way that we might perform addition of values in thenumbertype.
# let add n1 n2 = matchn1, n2 with
Zero, n | n, Zero ->
n
| Integer i1, Integer i2 -> Integer (i1 + i2) | Integer i, Real x | Real x, Integer i ->
Real (x +. float_of_int i) | Real x1, Real x2 ->
Real (x1 +. x2);;
val add : number -> number -> number = <fun>
# add x i;;
- : number = Real 4.2
There are a few things to note in this pattern matching. First, we are matching against the pair(n1, n2)of the numbersn1 andn2being added. The patterns are then pair
patterns. The first clause specifies that if the first number isZeroand the second isn,
or if the second number isZeroand the first isn, then the sum isn.
Zero, n | n, Zero ->
n
The second thing to note is that we are able to collapse some of the cases that have similar patterns. For example, the code for addingInteger andRealvalues is the
same, whether the first number is anIntegerorReal. In both cases, the variableiis
bound to theIntegervalue, andxto theRealvalue.
OCaml allows two patternsp1andp2to be combined into a choice patternp1 | p2 under two conditions: both patterns must define the same variables; and, the value being matched by multiple occurrences of a variable must have the same types. Other- wise, the placement of variables inp1andp2is unrestricted.
In the remainder of this chapter we will describe the the disjoint union type more completely, using a running example for building balanced binary trees, a frequently- used data structure in functional programs.
CHAPTER 6. UNIONS 6.1. BINARY TREES
6.1 Binary trees
Binary trees are often used for representing collections of data. For our purposes, a binary tree is an acyclic graph, where each node (vertex) has either zero or two nodes calledchildren. If noden2 is a child ofn1, thenn1is called theparentof n2. One node, called theroot, has no parents; all other nodes have exactly one parent.
One way to represent this data structure is by defining a disjoint union for the type of a node and its children. Since each node has either zero or two children, we need two cases. The following definition defines the type for a labeled tree: the type variable’arepresents the type of labels; the constructorNoderepresents a node with
two children; and the constructorLeafrepresents a node with no children. Note that the type’a treeis defined with a type parameter’afor the type of labels. This type definition is recursive—the type’a treeis mentioned in its own definition.
# type ’a tree =
Node of ’a * ’a tree * ’a tree | Leaf;;
type ’a tree = | Node of ’a * ’a tree * ’a tree | Leaf
The use of tuple types in a constructor definition (for example,
Node of ’a * ’a tree * ’a tree) is quite common, and has an efficient im-
plementation. When applying a constructor, parentheses are required around the elements of the tuple. In addition, even though constructors that take arguments are similar to functions, they are not functions, and may not be used as values.
#Leaf;;
- : ’a btree = Leaf
#Node (1, Leaf, Leaf);;
- : int btree = Node (1, Leaf, Leaf)
#Node;;
The constructor Node expects 3 argument(s), but is here applied to 0 argument(s)
Since the type definition for’a treeis recursive, many of the functions defined on
the tree will also be recursive. For example, the following function defines one way to count the number of non-leaf nodes in the tree.
# let reccardinality =function Leaf -> 0
| Node (_, left, right) ->
cardinality left + cardinality right + 1;;
val cardinality : ’a btree -> int = <fun>
#cardinality (Node (1, Node (2, Leaf, Leaf), Leaf));;
- : int = 2