OCaml from the Very Beginning

In OCaml from the Very Beginning John Whitington takes a no-prerequisites approach to teaching a modern general-purpose programming language. Each small, self-contained chapter introduces a new topic, building until the reader can write quite substantial programs. There are plenty of questions and, crucially, worked answers and hints.

OCaml from the Very Beginning will appeal both to new programmers, and experienced programmers eager to explore functional languages such as OCaml. It is suitable both for formal use within an undergraduate or graduate curriculum, and for the interested amateur.

John Whitington founded a software company which uses OCaml extensively. He teaches functional programming to students of Computer Science at the University of Cambridge.

Published in the United Kingdom by Coherent Press, Cambridge

Copyright Coherent Press 2013. Last updated 2022.

This publication is in copyright. Subject to statutory exception no reproduction of any part may take place without the written permission of Coherent Press. A catalogue record for this book is available from the British Library.

ISBN 978-0-9576711-0-2 Paperback

Preface

This book is based on the Author’s experience of teaching programming to students in the University of Cambridge supervisions system. In particular, working with students for the first-year undergraduate course “Foundations of Computer Science”, based on Standard ML and lectured for many years by Lawrence C. Paulson.

An interesting aspect of supervising students from a wide range of backgrounds (some with no previous experience at all taking Computer Science as an additional subject within the Cambridge Natural Sciences curriculum, and some with a great deal of programming experience already) is the level playing field which the ML family of languages (like OCaml) provide. Sometimes, those students with least prior programming experience perform the best.

I have tried to write a book which has no prerequisites – and with which any intelligent undergraduate ought to be able to cope, whilst trying to be concise enough that someone coming from another language might not be too annoyed by the tone.

Special note to those who have already written programs

When I was a boy, our class was using a word processor for the first time. I wanted a title for my story, so I typed it on the first line and then, placing the cursor at the beginning, held down the space bar until the title was roughly in the middle. My friend taught me how to use the centring function, but it seemed more complicated to me, and I stuck with the familiar way – after all, it worked. Later on, of course, when I had more confidence and experience, I realized he had been right.

When starting a language which is fundamentally different from those you have seen before, it can be difficult to see the advantages, and to try to think of every concept in terms of the old language. I would urge you to consider the possibility that, at the moment, you might be the boy holding down the space bar.

Acknowledgments

Inevitably, the approach here owes a debt to that taken by Lawrence C. Paulson, both in his lecture notes and in his book “ML for the Working Programmer” (Cambridge University Press, 1996). Question 3 in Chapter 11 is inspired by an examination question of his. I was taught Standard ML by Professor Paulson and Andrei Serjantov in Autumn 2000. Mark Shinwell has been a constant source of helpful discussion. Robin Walker and latterly Andrew Rice have arranged the supervisions system at Queens’ College within which I have taught since 2004. I am grateful to the developers of OCaml who have provided such a pleasant environment in which to write programs. Helpful comments on an earlier draft were provided by Martin DeMello, Damien Doligez, Arthur Guillon, Zhi Han, Robert Jakob, Xavier Leroy, Florent Monnier, and Benjamin Pierce. And, of course, I thank all my students, some of whom are now working with OCaml for a living.

Getting Ready

This book is about teaching the computer to do new things by writing computer programs. Just as there are different languages for humans to speak to one another, there are different programming languages for humans to speak to computers.

We are going to be using a programming language called OCaml. It might already be on your computer, or you may have to find it on the internet and install it yourself. You will know that you have OCaml working when you see something like this:

OCaml

#

OCaml is waiting for us to type something. Try typing 1 + 2;; followed by the Enter key. You should see this:

OCaml

# 1 + 2;;
- : int = 3

OCaml is telling us the result of the calculation. To leave OCaml, give the exit 0 command, again ending with ;; to tell OCaml we have finished typing:

OCaml

# exit 0;;

You should find yourself back where you were before. If you make a mistake when typing, you can press Ctrl-C (hold down the Ctrl key and tap the c key). This will allow you to start again:

OCaml

# 1 + 3^CInterrupted
# 1 + 2;;
- : int = 3

We are ready to begin.

Starting Off

We will cover a fair amount of material in this chapter and its questions, since we will need a solid base on which to build. You should read this with a computer running OCaml in front of you.

Consider first the mathematical expression 1 + 2 × 3. What is the result? How did you work it out? We might show the process like this:

image

How did we know to multiply 2 by 3 first, instead of adding 1 and 2? How did we know when to stop? Let us underline the part of the expression which is dealt with at each step:

image

We chose which part of the expression to deal with each time using a familiar mathematical rule – multiplication is done before addition. We stopped when the expression could not be processed any further.

Computer programs in OCaml are just like these expressions. In order to give you an answer, the computer needs to know all the rules you know about how to process the expression correctly. In fact, 1 + 2 × 3 is a valid OCaml expression as well as a valid mathematical one, but we must write * instead of ×, since there is no × key on the keyboard:

OCaml

# 1 + 2 * 3;;
- : int = 7

Here, # is OCaml prompting us to write an expression, and 1 + 2 * 3;; is what we typed (the semicolons followed by the Enter key tell OCaml we have finished our expression). OCaml responds with the answer 7. OCaml also prints int, which tells us that the answer is a whole number, or integer.

Let us look at our example expression some more. There are two operators: + and ×. There are three operands: 1, 2, and 3. When we wrote it down, and when we typed it into OCaml, we put spaces between the operators and operands for readability. How does OCaml process it? Firstly, the text we wrote must be split up into its basic parts: 1, +, 2, *, and 3. OCaml then looks at the order and kind of the operators and operands, and decides how to parenthesize the expression: (1+(2×3)). Now, processing the expression just requires dealing with each parenthesized section, starting with the innermost, and stopping when there are no parentheses left:

image

OCaml knows that × is to be done before +, and parenthesizes the expression appropriately. We say the × operator has higher precedence than the + operator.

An expression is any valid OCaml program. To produce an answer, OCaml evaluates the expression, yielding a special kind of expression, a value. In our previous example, 1 + 2 × 3, 1 + 6, and 7 were all expressions, but only 7 was a value.

Each expression (and so each value) has a type. The type of 7 is int (it is an integer). The types of the expressions 1 + 6 and 1 + 2 × 3 are also int, since they will evaluate to a value of type int. The type of any expression may be worked out by considering the types of its sub-expressions, and how they are combined to form the expression. For example, 1 + 6 has type int because 1 is an int, 6 is an int, and the + operator takes two integers and gives another one (their sum). Here are the mathematical operators on integers:

Operator Description
a + b addition
a - b subtract b from a
a * b multiplication
a / b divide a by b, returning the whole part
a mod b divide a by b, returning the remainder

The mod, *, and / operators have higher precedence than the + and - operators. For any operator above, the expression a ⊕ b ⊕ c is equivalent to (a ⊕ b) ⊕ c rather than a ⊕ (b ⊕ c) (we say the operators are left associative). We sometimes write down the type of an expression after a colon when working on paper, to keep it in mind:

5 * -2 : int

(negative numbers are written with - before them). Of course, there are many more types than just int. Sometimes, instead of numbers, we would like to talk about truth: either something is true or it is not. For this we use the type bool which represents boolean values, named after the English mathematician George Boole (1815–1864) who pioneered their use. There are just two things of type bool:

true
false

How can we use these? One way is to use one of the comparison operators, which are used for comparing values to one another. For example:

OCaml

# 99 > 100;;
- : bool = false
# 4 + 3 + 2 + 1 = 10;;
- : bool = true

Here are all the comparison operators:

Operator Description
a = b true if a and b are equal
a < b true if a is less than b
a <= b true if a is less than or equal to b
a > b true if a is more than b
a >= b true if a is more than or equal to b
a <> b true if a is not equal to b

Notice that if we try to use operators with types for which they are not intended, OCaml will not accept the program at all, showing us where our mistake is by underlining it:

OCaml

# 1 + true;;
Error: This expression has type bool but an expression was expected of type
int

You can find more information about error messages in OCaml in the appendix “Coping with Errors” at the back of this book.

There are two operators for combining boolean values (for instance, those resulting from using the comparison operators). The expression a && b evaluates to true only if expressions a and b both evaluate to true. The expression a || b evaluates to true only if a evaluates to true, b evaluates to true, or both do. The && operator (pronounced “and”) is of higher precedence than the || operator (pronounced “or”), so a && b || c is the same as (a && b) || c.

A third type we shall be using is char which holds a single character, such as ‘a’ or ‘?’. We write these in single quotation marks:

OCaml

# ’c’;;
- : char = ’c’

So far we have looked only at operators like +, mod, and = which look like familiar mathematical ones. But many constructs in programming languages look a little different. For example, to choose a course of evaluation based on some test, we use the if … then … else construct:

OCaml

# if 100 > 99 then 0 else 1;;
- : int = 0

The expression between if and then (in our example 100 > 99) must have type bool – it evaluates to either true or false. The types of the expression to choose if true and the expression to choose if false must be the same as one another – here they are both of type int. The whole expression evaluates to the same type – int – because either the then part or the else part is chosen to be the result of evaluating the whole expression:

image

We have covered a lot in this chapter, but we need all these basic tools before we can write interesting programs. Make sure you work through the questions on paper, on the computer, or both, before moving on. Hints and answers are at the back of the book.

Questions

  1. What are the types of the following expressions and what do they evaluate to, and why?

    17

    1 + 2 * 3 + 4

    800 / 80 / 8

    400 > 200

    1 <> 1

    true || false

    true && false

    if true then false else true

    ’%’

    ’a’ + ’b’

  2. Consider the evaluations of the expressions 1 + 2 mod 3, (1 + 2) mod 3, and 1 + (2 mod 3). What can you conclude about the + and mod operators?

  3. A programmer writes 1+2 * 3+4. What does this evaluate to? What advice would you give him?

  4. The range of numbers available is limited. There are two special numbers: min_int and max_int. What are their values on your computer? What happens when you evaluate the expressions max_int + 1 and min_int - 1?

  5. What happens when you try to evaluate the expression 1 / 0? Why?

  6. Can you discover what the mod operator does when one or both of the operands are negative? What about if the first operand is zero? What if the second is zero?

  7. Why not just use, for example, the integer 0 to represent false and the integer 1 for true? Why have a separate bool type at all?

  8. What is the effect of the comparison operators like < and > on alphabetic values of type char? For example, what does ’p’ < ’q’ evaluate to? What is the effect of the comparison operators on the booleans, true and false?

Names and Functions

So far we have built only tiny toy programs. To build bigger ones, we need to be able to name things so as to refer to them later. We also need to write expressions whose result depends upon one or more other things.

Before, if we wished to use a sub-expression twice or more in a single expression, we had to type it multiple times:

OCaml

# 200 * 200 * 200;;
- : int = 8000000

Instead, we can define our own name to stand for the result of evaluating an expression, and then use the name as we please:

OCaml

# let x = 200;;
val x : int = 200
# x * x * x;;
- : int = 8000000

To write this all in a single expression, we can use the let  …  =  … in  construct:

OCaml

# let x = 200 in x * x * x;;
- : int = 8000000
# let a = 500 in (let b = a * a in a + b);;
- : int = 250500

We can also make a function, whose value depends upon some input (we call this input an argument – we will be using the word “input” later in the book to mean something different):

OCaml

# let cube x = x * x * x;;
val cube : int -> int = <fun>
# cube 200;;
- : int = 8000000

We chose cube for the name of the function and x for the name of its argument. When we typed the function in, OCaml replied by telling us that its type is int int. This means it is a function which takes an integer as its argument, and, when given that argument, evaluates to an integer. To use the function, we just write its name followed by a suitable argument. In our example, we calculated 2003 by giving the cube function 200 as its argument.

The cube function has type int int, we gave it an integer 200, and so the result is another integer. Thus, the type of the expression cube 200 is int – remember that the type of any expression is the type of the thing it will evaluate to, and cube 200 evaluates to 8000000, an integer. In diagram form:

image

If we try an argument of the wrong type, the program will be rejected:

OCaml

# let cube x = x * x * x;;
val cube : int -> int = <fun>
# cube false;;
Error: This expression has type bool but an expression was expected of type
int

Here is a function which determines if an integer is negative:

OCaml

# let neg x = if x < 0 then true else false;;
val neg : int -> bool = <fun>
# neg (-30);;
- : bool = true

(We added parentheses to distinguish from neg - 30). But, of course, this is equivalent to just writing

OCaml

# let neg x = x < 0;;
val neg : int -> bool = <fun>
# neg (-30);;
- : bool = true

because x < 0 will evaluate to the appropriate boolean value on its own – true if x < 0 and false otherwise. Here is another function, this time of type char bool. It determines if a given character is a vowel or not:

OCaml

# let isvowel c =
    c = ’a’ || c = ’e’ || c = ’i’ || c = ’o’ || c = ’u’;;
val isvowel : char -> bool = <fun>
# isvowel ’x’;;
- : bool = false

Notice that we typed the function over two lines. This can be done by pressing the Enter key in between lines. OCaml knows that we are finished when we type ;; followed by Enter as usual. Notice also that we pressed space a few times so that the second line appeared a little to the right of the first. This is known as indentation and does not affect the meaning of the program at all – it is just for readability.

There can be more than one argument to a function. For example, here is a function which checks if two numbers add up to ten:

OCaml

# let addtoten a b =
a + b = 10;;
val addtoten : int -> int -> bool = <fun>
# addtoten 6 4;;
- : bool = true

The type is int int bool because the arguments are both integers, and the result is a boolean. We use the function in the same way as before, but writing two integers this time, one for each argument the function expects.

A recursive function is one which uses itself. Consider calculating the factorial of a given number – the factorial of 4 (written 4! in mathematics), for example, is 4 × 3 × 2 × 1. Here is a recursive function to calculate the factorial of a positive number. Note that it uses itself in its own definition.

OCaml

# let rec factorial a =
if a = 1 then 1 else a * factorial (a - 1);;
val factorial : int -> int = <fun>
# factorial 4;;
- : int = 24

We used let rec instead of let to indicate a recursive function. How does the evaluation of factorial 4 proceed?

image

For the first three steps, the else part of the conditional expression is chosen, because the argument a is greater than one. When the argument is equal to one, we do not use factorial again, but just evaluate to one. The expression built up of all the multiplications is then evaluated until a value is reached: this is the result of the whole evaluation. It is sometimes possible for a recursive function never to finish – what if we try to evaluate factorial (-1)?

image

The expression keeps expanding, and the recursion keeps going. Helpfully, OCaml tells us what is going on:

OCaml

# let rec factorial a =
if a = 1 then 1 else a * factorial (a - 1);;
val factorial : int -> int = <fun>
# factorial (-1);;
Stack overflow during evaluation (looping recursion?).

This is an example of an error OCaml cannot find by merely looking at the program – it can only be detected during evaluation. Later in the book, we will see how to prevent people who are using our functions from making such mistakes.

One of the oldest methods for solving a problem (or algorithm) still in common use is Euclid’s algorithm for calculating the greatest common divisor of two numbers (that is, given two positive integers a and b, finding the biggest positive integer c such that neither a/c nor b/c have a remainder). Euclid was a Greek mathematician who lived about three centuries before Christ. Euclid’s algorithm is simple to write as a function with two arguments:

OCaml

# let rec gcd a b =
if b = 0 then a else gcd b (a mod b);;
val gcd : int -> int -> int = <fun>
# gcd 64000 3456;;
- : int = 128

Here is the evaluation:

image

Finally, here is a simple function on boolean values. In the previous chapter, we looked at the && and || operators which are built in to OCaml. The other important boolean operator is the not function, which returns the boolean complement (opposite) of its argument – true if the argument is false, and vice versa. This is also built in, but it is easy enough to define ourselves, as a function of type bool bool.

OCaml

# let not x =
if x then false else true;;
val not : bool -> bool = <fun>
# not true;;
- : bool = false

Almost every program we write will involve functions such as these, and many larger ones too. In fact, languages like OCaml are often called functional languages.

Questions

  1. Write a function which multiplies a given number by ten. What is its type?

  2. Write a function which returns true if both of its arguments are non-zero, and false otherwise. What is the type of your function?

  3. Write a recursive function which, given a number n, calculates the sum 1 + 2 + 3 + … + n. What is its type?

  4. Write a function power x n which raises x to the power n. Give its type.

  5. Write a function isconsonant which, given a lower-case character in the range ’a’’z’, determines if it is a consonant.

  6. What is the result of the expression  let x = 1 in let x = 2 in x + x ?

  7. Can you suggest a way of preventing the non-termination of the factorial function in the case of a zero or negative argument?

Note on Notation

From now on, instead of showing the actual OCaml session…

OCaml

# let rec factorial a =
if a = 1 then 1 else a * factorial (a - 1);;
val factorial : int -> int = <fun>

…we will usually just show the program in a box, together with its type:

image

If you prefer to compose your programs in a text editing program, and copy-and-paste them into OCaml, you can do that too. Just make sure you end with ;; to let OCaml know you have finished entering the program.

Later on, when we write larger programs, we will see how to use OCaml to load our programs from external files.

Case by Case

In the previous chapter, we used the conditional expression  if … then … else  to define functions whose results depend on their arguments. For some of them we had to nest the conditional expressions one inside another. Programs like this are not terribly easy to read, and expand quickly in size and complexity as the number of cases increases.

OCaml has a nicer way of expressing choices – pattern matching. For example, recall our factorial function:

image

We can rewrite this using pattern matching:

image

We can read this as “See if a matches the pattern 1. If it does, just return 1. If not, see if it matches the pattern _. If it does, the result is a * factorial (a - 1).” The pattern _ is special – it matches anything. Remember our isvowel function from the previous chapter?

image

Here is how to write it using pattern matching:

image

If we miss out one or more cases, OCaml will warn us:

OCaml

# let isvowel c =
    match c with
      ’a’ -> true
    | ’e’ -> true
    | ’i’ -> true
    | ’o’ -> true
    | ’u’ -> true;;
# Warning 8 [partial-match]: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
'b'
val isvowel : char -> bool

OCaml does not reject the program, because there may be legitimate reasons to miss cases out, but for now we will make sure all our pattern matches are complete. Notice that we had to repeat true five times. This would be awkward if the expression to be calculated was more complicated. We can combine patterns like this:

image

Finally, let us rewrite Euclid’s Algorithm from the previous chapter:

image

Now in pattern matching style:

image

The type of a whole  match  …  with … expression is determined by the types of the expressions on the right hand side of each -> arrow, all of which must be alike:

image

We use pattern matching whenever it is easier to read and understand than  if … then … else  expressions.

Questions

  1. Rewrite the not function from the previous chapter in pattern matching style.

  2. Use pattern matching to write a recursive function which, given a positive integer n, returns the sum of all the integers from 1 to n.

  3. Use pattern matching to write a function which, given two numbers x and n, computes xn.

  4. For each of the previous three questions, comment on whether you think it is easier to read the function with or without pattern matching. How might you expect this to change if the functions were much larger?

  5. What does match 1 + 1 with 2 -> match 2 + 2 with 3 -> 4 | 4 -> 5 evaluate to?

  6. There is a special pattern x..y to denote continuous ranges of characters, for example ’a’..’z’ will match all lowercase letters. Write functions islower and isupper, each of type char bool, to decide on the case of a given letter.

Making Lists

A list is a collection of elements. Here is a list of three integers:

[1; 2; 3]

We write a list between square brackets [ and ], separating the elements with semicolons. The list above has type int list, because it is a list of integers. All elements of the list must have the same type. The elements in the list are ordered (in other words, [1; 2; 3] and [2; 3; 1] are not the same list).

The first element is called the head, and the rest are collectively called the tail. In our example, the head is the integer 1 and the tail is the list [2; 3]. So you can see that the tail has the same type as the whole list. Here is a list with no elements (called “the empty list” or sometimes “nil”):

[]

It has neither a head nor a tail. Here is a list with just a single element:

[5]

Its head is the integer 5 and its tail is the empty list []. So every non-empty list has both a head and a tail. Lists may contain elements of any type: integers, booleans, functions, even other lists. For example, here is a list containing elements of type bool:

[false; true; false]  :  bool list

OCaml defines two operators for lists. The :: operator (pronounced “cons”) is used to add a single element to the front of an existing list:

image

The cons operation is completed in a constant amount of time, regardless of the length of the list. The @ operator (pronounced “append”) is used to combine two lists together:

image

This takes time proportional to the length of the list on the left hand side of the @ operator (that is, a list of length 100 will take roughly twice as long as one of length 50). We will see why soon.

Now, how do we write functions using lists? We can use pattern matching as usual, with some new types of pattern. For example, here’s a function which tells us if a list is empty:

image

The argument has type α list (which OCaml prints on the screen as ’a list) because this function does not inspect the individual elements of the list, it just checks if the list is empty. And so, this function can operate over any type of list. The greek letters α, β, γ etc. stand for any type. If two types are represented by the same greek letter they must have the same type. If they are not, they may have the same type, but do not have to. Functions like this are known as polymorphic. We can also use :: in our patterns, this time using it to deconstruct rather than construct the list:

image

Here is how the evaluation proceeds:

image

This works by recursion over the list, then addition of all the resultant 1s. It takes time proportional to the length of the list. Can you see why? It also takes space proportional to the length of the list, because of the intermediate expression 1 + (1 + (1 + … which is built up before any of the + operations are evaluated – this expression must be stored somewhere whilst it is being processed. Since h is not used in the expression 1 + length t, this function is also polymorphic. Indeed we can replace h in the pattern with _ since there is no use giving a name to something we are not going to refer to:

image

A very similar function can be used to add a list of integers:

image

However, since we are actually using the individual list elements (by adding them up), this function is not polymorphic – it operates over lists of type int list only. If we accidentally miss out a case, OCaml will alert us, and give an example pattern which is not matched:

image

There is a way to deal with the excessive space usage from the building up of a large intermediate expression 1 + 1 + 1 + … in our length function, at the cost of readability. We can “accumulate” the 1s as we go along in an extra argument. At each recursive step, the accumulating argument is increased by one. When we have finished, the total is returned:

image

We wrapped it up in another function to make sure we do not call it with a bad initial value for the accumulating argument. Here is an example evaluation:

image

Now, the space taken by the calculation does not relate in any way to the length of the list argument. Recursive functions which do not build up a growing intermediate expression are known as tail recursive. Functions can, of course, return lists too. Here is a function to return the list consisting of the first, third, fifth and so on elements in a list:

image

Consider the evaluation of odd_elements [2; 4; 2; 4; 2]:

image

You might notice that the first two cases in the pattern match return exactly their argument. By reversing the order, we can reduce this function to just two cases:

image

We have seen how to use the @ (append) operator to concatenate two lists:

image

How might we implement list append ourselves, if it was not provided? Consider a function append a b. If the list a is the empty list, the answer is simply b. But what if a is not empty? Then it has a head h and a tail t. So we can start our result list with the head, and the rest of the result is just append t b.

image

Consider the evaluation of append [1; 2; 3] [4; 5; 6]:

image

This takes time proportional to the length of the first list – the second list need not be processed at all. What about reversing a list? For example, we want rev [1; 2; 3; 4] to evaluate to [4; 3; 2; 1]. One simple way is to reverse the tail of the list, and append the list just containing the head to the end of it:

image

Here’s how the evaluation proceeds:

image

This is a simple definition, but not very efficient – can you see why?

Two more useful functions for processing lists are take and drop which, given a number and a list, either take or drop that many elements from the list:

image

For example, here’s the evaluation for take 2 [2; 4; 6; 8; 10]:

image

And for drop 2 [2; 4; 6; 8; 10]:

image

Note that these functions contain incomplete pattern matches – OCaml tells us so when we type them in. The function fails if the arguments are not sensible – that is, when we are asked to take or drop more elements than are in the argument list. Later on, we will see how to deal with that problem. Note also that for any sensible value of n, including zero, take n l and drop n l split the list into two parts with no gap. So drop and take often appear in pairs.

Lists can contain anything, so long as all elements are of the same type. So, of course, a list can contain lists. Here’s a list of lists of integers:

[[1]; [2; 3]; [4; 5; 6]]  :  (int list) list

Each element of this list is of type int list (we can also omit the parentheses and write int list list). Within values of this type, it is important to distinguish the list of lists containing no elements

[] : α list list

from the list of lists containing one element which is the empty list

[[]] : α list list

Questions

  1. Write a function evens which does the opposite to odds, returning the even numbered elements in a list. For example, evens [2; 4; 2; 4; 2] should return [4; 4]. What is the type of your function?

  2. Write a function count_true which counts the number of true elements in a list. For example, count_true [true; false; true] should return 2. What is the type of your function? Can you write a tail recursive version?

  3. Write a function which, given a list, builds a palindrome from it. A palindrome is a list which equals its own reverse. You can assume the existence of rev and @. Write another function which determines if a list is a palindrome.

  4. Write a function drop_last which returns all but the last element of a list. If the list is empty, it should return the empty list. So, for example, drop_last [1; 2; 4; 8] should return [1; 2; 4]. What about a tail recursive version?

  5. Write a function member of type α α list bool which returns true if an element exists in a list, or false if not. For example, member 2 [1; 2; 3] should evaluate to true, but member 3 [1; 2] should evaluate to false.

  6. Use your member function to write a function make_set which, given a list, returns a list which contains all the elements of the original list, but has no duplicate elements. For example, make_set [1; 2; 3; 3; 1] might return [2; 3; 1]. What is the type of your function?

  7. Can you explain why the rev function we defined is inefficient? How does the time it takes to run relate to the size of its argument? Can you write a more efficient version using an accumulating argument? What is its efficiency in terms of time taken and space used?

Two Different Ways of Thinking

Look again at our list appending function:

image

There are two ways to think about this computation. One way is to imagine the actions the computer might take to calculate the result:

Look at the first list. If it is empty, return the second list. Otherwise, pull apart the first list, looking at its head and tail. Make a recursive call to append the tail to the second list, and then cons the head onto the result. Return this.

Alternatively, we can consider each match case to be an independent statement of truth, thinking the same way about the whole function:

The empty list appended to another list is that list. Otherwise, the first list is non-empty, so it has a head and a tail. Call them h and t. Clearly append (h :: t) b is equal to h :: append t b. Since this reduces the problem size, progress is made.

It is very useful to be able to think in these two ways about functions you write, and to be able to swap between them in the mind with ease.

Sorting Things

Lists often need to be in sorted order. How might we write a function to sort a list of integers? Well, a list with zero elements is already sorted. If we do not have an empty list, we must have a head and a tail. What can we do with those? Well, we can sort the tail by a recursive call to our sort function. So, now we have the head, and an already sorted list. Now, we just need to write a function to insert the head in an already sorted list. We have reduced the problem to an easier one.

image

Now we just need to write the insert function. This takes an element and an already-sorted list, and returns the list with the element inserted in the right place:

image

Consider the evaluation of insert 3 [1; 1; 2; 3; 5; 9]:

image

Here is the whole evaluation of sort [53; 9; 2; 6; 19]. We have missed out the detail of each insert operation.

image

Here’s the full program, known as insertion sort:

image

Notice that the type α list α list rather than int list int list. This is because OCaml’s comparison functions like <= (used inside insert) work for types other than int. For example, OCaml knows how to compare characters in alphabetical order:

image

How long does our sorting function take to run if the list to be sorted has n elements? Under the assumption that our argument list is arbitrarily ordered rather than sorted, each insert operation takes time proportional to n (the element might need to be inserted anywhere). We must run the insert function as many times as there are elements so, adding these all up, the sort function takes time proportional to n2. You might argue that the first insert operations only have to work with a very small list, and that this fact should make the time less that n2. Can you see why that is not true? What happens if the list is sorted already?

A more efficient algorithm can be found by considering a basic operation a little more complex than insert, but which still operates in time proportional to the length of the argument list. Such a function is merge, which takes two already sorted lists, and returns a single sorted list:

image

When x and y are both the empty list, the first case is picked because l matches the empty list. Here is how merge proceeds:

image

So merge can take two sorted lists, and produce a longer, sorted list, containing all the elements from both lists. So, how can we use this to sort a list from scratch? Well, we can use length, take, and drop from the previous chapter to split the list into two halves. Now, we must use a recursive call to sort each half, and then we can merge them. This is known as merge sort.

image

The case for the single element is required because, if we split it into two halves, of length one and zero, the recursion would not end – we would not have reduced the size of the problem.

How does msort work? Consider the evaluation of msort on the list [53; 9; 2; 6; 19]. We will skip the evaluation of the merge, drop, take, and length functions, concentrating just on msort:

image

From now on we will not be showing these full evaluations all the time – but when you are unsure of how or why a function works, you can always write them out on paper yourself.

How long does it take?

How long does merge sort take to run? We can visualize it with the following diagram, in which we have chosen a list of length eight (a power of two) for convenience.

[6; 4; 5; 7; 2; 5; 3; 4]
[6; 4; 5; 7][2; 5; 3; 4]
[6; 4][5; 7][2; 5][3; 4]
[6][4][5][7][2][5][3][4]
[4; 6][5; 7][2; 5][3; 4]
[4; 5; 6; 7][2; 3; 4; 5]
[2; 3; 4; 4; 5; 5; 6; 7]

In the top half of the diagram, the lists are being taken apart using take and drop, until they are small enough to already be sorted. In the bottom half, they are being merged back together.

How long does each row take? For the top half: to split a list into two halves takes time proportional to the length of the list. On the first line, we do this once on a list of length eight. On the second line, we do it twice on lists of length four, and so on. So each line takes the same time overall. For the bottom half, we have another function which takes time proportional to the length of its argument – merge – so each line in the bottom half takes time proportional to the length too.

So, how many lines do we have? Well, in the top half we have roughly log2n, and the same for the bottom half. So, the total work done is 2 × log2n × n, which is proportional to nlog2n.

Questions

  1. In msort, we calculate the value of the expression length l / 2 twice. Modify msort to remove this inefficiency.

  2. We know that take and drop can fail if called with incorrect arguments. Show that this is never the case in msort.

  3. Write a version of insertion sort which sorts the argument list into reverse order.

  4. Write a function to detect if a list is already in sorted order.

  5. We mentioned that the comparison functions like  <  work for many OCaml types. Can you determine, by experimentation, how they work for lists? For example, what is the result of [1; 2] < [2; 3]? What happens when we sort the following list of type char list list? Why?

    [[’o’; ’n’; ’e’]; [’t’; ’w’; ’o’]; [’t’; ’h’; ’r’; ’e’; ’e’]]

  6. Combine the sort and insert functions into a single sort function.

Loading a Program from a File

Now that we are building larger functions, we might like to store them between sessions, rather than typing them in every time. For example, compose a file like this in a text editor:

image

Save the file in same directory (folder) as you enter OCaml from, under the name lists.ml. We can then tell OCaml to use the contents of that file like this:

image

It is exactly the same as typing it in manually – the functions length and append will now be available for use. Errors and warnings will be reported as usual. Note that the #use command is not part of the OCaml language for expressions – it is just a command we are giving to OCaml.

Functions upon Functions upon Functions

Often we need to apply a function to every element of a list. For example, doubling each of the numbers in a list of integers. We could do this with a simple recursive function, working over each element of a list:

image

For example,

image

The result list does not need to have the same type as the argument list. We can write a function which, given a list of integers, returns the list containing a boolean for each: true if the number is even, false if it is odd.

image

For example,

image

It would be tedious to write a similar function each time we wanted to apply a different operation to every element of a list – can we build one which works for any operation? We will use a function as an argument:

image

The map function takes two arguments: a function which processes a single element, and a list. It returns a new list. We will discuss the type in a moment. For example, if we have a function halve:

image

We can use map like this:

image

Now, let us look at that type: (α β) α list β list. We can annotate the individual parts:

image

We have to put the function f in parentheses, otherwise it would look like map had four arguments. It can have any type α β. That is to say, it can have any argument and result types, and they do not have to be the same as each other – though they may be. The argument has type α list because each of its elements must be an appropriate argument for f. In the same way, the result list has type β list because each of its elements is a result from f (in our halve example, α and β were both int). We can rewrite our evens function to use map:

image

In this use of map, α was int, β was bool. We can make evens still shorter: when we are just using a function once, we can define it directly, without naming it:

image

This is called an anonymous function. It is defined using fun, a named argument, the -> arrow and the function definition (body) itself. For example, we can write our halving function like this:

fun x -> x / 2

and, thus, write:

image

We use anonymous functions when a function is only used in one place and is relatively short, to avoid defining it separately.

In the preceding chapter we wrote a sorting function and, in one of the questions, you were asked to change the function to use a different comparison operator so that the function would sort elements into reverse order. Now, we know how to write a version of the msort function which uses any comparison function we give it. A comparison function would have type α α bool. That is, it takes two elements of the same type, and returns true if the first is “greater” than the second, for some definition of “greater” – or false otherwise.

So, let us alter our merge and msort functions to take an extra argument – the comparison function:

image

Now, if we make our own comparison operator:

image

we can use it with our new version of the msort function:

image

In fact, we can ask OCaml to make such a function from an operator such as <= or + just by enclosing it in parentheses and spaces:

OCaml

# ( <= )
- : 'a -> 'a -> bool = <fun>
# ( <= ) 4 5
- : bool = true

So, for example:

image

and

image

The techniques we have seen in this chapter are forms of program reuse, which is fundamental to writing manageable large programs.

Questions

  1. Write a simple recursive function calm to replace exclamation marks in a char list with periods. For example calm [’H’; ’e’; ’l’; ’p’; ’!’; ’ ’; ’F’; ’i’; ’r’; ’e’; ’!’] should evaluate to [’H’; ’e’; ’l’; ’p’; ’.’; ’ ’; ’F’; ’i’; ’r’; ’e’; ’.’]. Now rewrite your function to use map instead of recursion. What are the types of your functions?

  2. Write a function clip which, given an integer, clips it to the range 1…10 so that integers bigger than 10 round down to 10, and those smaller than 1 round up to 1. Write another function cliplist which uses this first function together with map to apply this clipping to a whole list of integers.

  3. Express your function cliplist again, this time using an anonymous function instead of clip.

  4. Write a function apply which, given another function, a number of times to apply it, and an initial argument for the function, will return the cumulative effect of repeatedly applying the function. For instance, apply f 6 4 should return f (f (f (f (f (f 4)))))). What is the type of your function?

  5. Modify the insertion sort function from the preceding chapter to take a comparison function, in the same way that we modified merge sort in this chapter. What is its type?

  6. Write a function filter which takes a function of type α bool and an α list and returns a list of just those elements of the argument list for which the given function returns true.

  7. Write the function for_all which, given a function of type α bool and an argument list of type α list evaluates to true if and only if the function returns true for every element of the list. Give examples of its use.

  8. Write a function mapl which maps a function of type α β over a list of type α list list to produce a list of type β list list.

When Things Go Wrong

Some of the functions we have written so far have had a single, correct answer for each possible argument. For example, there’s no number we cannot halve. However, when we use more complicated types such as lists, there are plenty of functions which do not always have an answer – a list might not have a head or a tail, for example. Our take and drop functions were unsatisfactory in case of invalid arguments. For example, take 3 [’a’] would simply return []. This is bad practice – we are hiding errors rather than confronting them.

OCaml has a mechanism for reporting such run-time errors (these are quite different from the type errors OCaml reports when it refuses to accept a program at all). This mechanism is exceptions.

There are some built-in exceptions in OCaml. For example Division_by_zero, which is raised when a program tries to divide a number by zero:

OCaml

# 10 / 0;;
Exception: Division_by_zero.

In order to signal bad arguments in functions like take and drop, we can rewrite them using the built-in exception Invalid_argument, which also carries a message written between double quotation marks. Typically we use this to record the name of the function which failed. The following program shows take and drop rewritten to use the Invalid_argument exception using raise. Note that these functions deal with two problems of our previous versions: a negative argument, and being asked to take or drop more than the number of elements in the list.

image

We can define our own exceptions, using exception. They can carry information along with them, of a type we choose:

OCaml

# exception Problem;;
exception Problem
# exception NotPrime of int;;
exception NotPrime of int

We have defined two exceptions – Problem, and NotPrime which carries an integer along with it. Exceptions must start with a capital letter. The of construct can be used to introduce the type of information which travels along with an exception. Once they are defined we may use them in our own functions, using raise:

OCaml

# exception Problem;;
exception Problem
# let f x = if x < 0 then raise Problem else 100 / x;;
val f : int -> int = <fun>
# f 5
- : int = 20
# f (-1);;
Exception: Problem.

Exceptions can be handled as well as raised. An exception handler deals with an exception raised by an expression. Exception handlers are written using the try … with construct:

image

The safe_divide function tries to divide x by y, but if the expression x / y raises the built-in exception Division_by_zero, instead we return zero. Thus, our safe_divide function succeeds for every argument.

How do the types work here? The expression x / y has type int and so the expression we substitute in case of Division_by_zero must have the same type: int, which indeed it does. And so, our rule that each expression must have one and only one type is not violated – safe_divide always returns an int.

image

Here is another example. The function last returns the last element of a list:

image

The pattern match is incomplete, so whilst OCaml accepts the program it can fail at run-time. We can tidy up the situation by raising the built-in exception Not_found:

image

The type of a function gives no indication of what exceptions it might raise or handle; it is the responsibility of the programmer to ensure that exceptions which should be handled always are – this is an area in which the type system cannot help us. Later in this book, we will see some alternatives to exceptions for occasions when they are likely to be frequently raised, allowing the type system to make sure we have dealt with each possible circumstance.

Questions

  1. Write a function smallest which returns the smallest positive element of a list of integers. If there is no positive element, it should raise the built-in Not_found exception.

  2. Write another function smallest_or_zero which uses the smallest function but if Not_found is raised, returns zero.

  3. Write an exception definition and a function which calculates the largest integer smaller than or equal to the square root of a given integer. If the argument is negative, the exception should be raised.

  4. Write another function which uses the previous one, but handles the exception, and simply returns zero when a suitable integer cannot be found.

  5. Comment on the merits and demerits of exceptions as a method for dealing with exceptional situations, in contrast to returning a special value to indicate an error (such as -1 for a function normally returning a positive number).

Looking Things Up

Many programs make use of a structure known as a dictionary. A real dictionary is used for associating definitions with words; we use “dictionary” more generally to mean associating some unique keys (like words) with values (like definitions). For example, we might like to store the following information about the number of people living in each house in a road:

image

The house number is the key, the number of people living in the house is the value. The order of keys is unimportant – we just need to be able to associate each key with one (and only one) value. It would be very inconvenient to store two lists, one of house numbers and one of people. For one thing, we would have way of guaranteeing the two lists were of equal length. What we would like is a way of representing pairs like (1, 4) and then having a single list of those. To make a pair in OCaml, just write it with parentheses and a comma:

image

It has the type int × int, which we pronounce as “int cross int”. When printed on the screen, * is used instead of × just as with multiplication. The two parts of the pair need not have the same type:

image

We can write simple functions to extract the first and second element using pattern matching:

image

In fact, since pairs can only take one form (unlike lists, which have two forms: empty or consisting of a head and a tail), OCaml lets us use the pattern directly in place of the argument:

image

Now, we can store a dictionary as a list of pairs:

image

Notice the parentheses around int × int in the type. Otherwise, it would be the type of a pair of an integer and an integer list:

image

What operations might we want on dictionaries? We certainly need to look up a value given a key:

image

For example, lookup 4 census evaluates to 3, whereas lookup 9 census raises Not_found. Another basic operation is to add an entry (we must replace it if it already exists, to maintain the property that each key appears at most once in a dictionary).

image

For example, add 6 2 [(4, 5); (6, 3)] evaluates to [(4, 5); (6, 2)] (the value for key 6 is replaced), whereas add 6 2 [(4, 5); (3, 6)] evaluates to [(4, 5); (3, 6); (6, 2)] (the new entry for key 6 is added). Removing an element is easy:

image

The function always succeeds – even if the key was not found. We can use exception handling together with our lookup operation to build a function which checks if a key exists within a dictionary:

image

If lookup k d succeeds, true will be returned. If not, an exception will be raised, which key_exists will handle itself, and return false. Note that we did not give a name to the result of lookup k l because we always return true if it succeeds.

Pairs are just a particular instance of a more general construct – the tuple. A tuple may contain two or more things. For example, (1, false, ’a’) has type int × bool × char.

Questions

  1. Write a function to determine the number of different keys in a dictionary.

  2. Define a function replace which is like add, but raises Not_found if the key is not already there.

  3. Write a function to build a dictionary from two equal length lists, one containing keys and another containing values. Raise the exception Invalid_argument if the lists are not of equal length.

  4. Now write the inverse function: given a dictionary, return the pair of two lists – the first containing all the keys, and the second containing all the values.

  5. Define a function to turn any list of pairs into a dictionary. If duplicate keys are found, the value associated with the first occurrence of the key should be kept.

  6. Write the function union a b which forms the union of two dictionaries. The union of two dictionaries is the dictionary containing all the entries in one or other or both. In the case that a key is contained in both dictionaries, the value in the first should be preferred.

More with Functions

Look again at the type of a simple function with more than one argument:

image

We have been considering functions like this as taking two arguments and returning a result. In fact, the truth is a little different. The type int int int can also be written as int (int int). OCaml lets us omit the parentheses because is a right-associative operator in the language of types. This gives us a clue.

In truth, the function add is a function which, when you give it an integer, gives you a function which, when you give it an integer, gives the sum.

This would be of no particular interest to us, except for one thing: we can give a function with two arguments just one argument at a time, and it turns out to be rather useful. For example:

OCaml

# let add x y = x + y
val add : int -> int -> int = <fun>
# let f = add 6
val f : int -> int = <fun>
# f 5
- : int = 11

Here, we have defined a function f by applying just one argument to add. This gives a function of type int int which adds six to any number. We then apply 5 to this function, giving 11. When defining f, we used partial application (we applied only some of the arguments). In fact, even when applying all the arguments at once, we could equally write (add 6) 5 rather than add 6 5. We can add six to every element in a list:

map (add 6) [10; 20; 30]

Here, add 6 has the type int int, which is an appropriate type to be the first argument to map when mapping over a list of integers. We can use partial application to simplify some examples from earlier in the book. We mentioned that you can write, for example, ( * ) to produce a function from an operator. It has type int int int. We may partially apply this function, so instead of writing

map (fun x -> x * 2) [10; 20; 30]

we may write

map (( * ) 2) [10; 20; 30]

Recall the function to map something over a list of lists from the questions to Chapter 6:

image

With partial application, we can write

image

Can you see why? The partially applied function map f is of type α list β list, which is exactly the right type to pass to map when mapping over lists of lists. In fact, we can go even further and write:

image

Here,  map (map f) has type α list list β list list so when an f is supplied to mapl, a function is returned requiring just the list. This is partial application at work again.

You can see the real structure of multiple-argument functions, by writing add using anonymous functions:

image

This makes it more obvious that our two-argument add function is really just composed of one-argument functions, but let add x y = x + y is much clearer! We can apply one or more arguments at a time, but they must be applied in order. Everything in this chapter also works for functions with more than two arguments.

Summary

The function f x y 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 something of type γ. And so, we can apply just one argument to the function f (which is called partial application), or apply both at once. When we write let f x y = … this is just shorthand for let f = fun x -> fun y -> …

Questions

  1. Rewrite the summary paragraph at the end of this chapter for the three argument function g a b c.

  2. Recall the function member x l which determines if an element x is contained in a list l. What is its type? What is the type of member x? Use partial application to write a function member_all x ls which determines if an element is a member of all the lists in the list of lists ls.

  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.

  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 like map, mapl, or mapll depending upon the list given to it?

  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.

  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.

New Kinds of Data

So far, we have considered the simple types int, bool, char, the compound type list, and tuples. We have built functions from and to these types. It would be possible to encode anything we wanted as lists and tuples of these types, but it would lead to complex and error-strewn programs. It is time to make our own types. New types are introduced using type. Here’s a type for colours:

OCaml

# type colour = Red | Green | Blue | Yellow;;
type colour = Red | Green | Blue | Yellow

The name of our new type is colour. It has four constructors, written with an initial capital letter: Red, Green, Blue, and Yellow. These are the possible forms a value of type colour may take. Now we can build values of type colour:

image

Let us extend our type to include any other colour which can be expressed in the RGB (Red, Green, Blue) colour system (each component ranges from 0 to 255 inclusive, a standard range giving about 16 million different colours).

image

We use of in our new constructor, to carry information along with values built with it. Here, we are using something of type int × int × int. Notice that the list cols of type colour list contains varying things, but they are all of the same type, as required by a list. We can write functions by pattern matching over our new type:

image

Types may contain a type variable like α to allow the type of part of the new type to vary – i.e. for the type to be polymorphic. For example, here is a type used to hold either nothing, or something of any type:

OCaml

# type 'a option = None | Some of 'a;;
type 'a option = None | Some of 'a

We can read this as “a value of type α option is either nothing, or something of type α”. For example:

image

The option type is useful as a more manageable alternative to exceptions where the lack of an answer is a common (rather than genuinely exceptional) occurrence. For example, here is a function to look up a value in a dictionary, returning None instead of raising an exception if the value is not found:

image

Now, there is no need to worry about exception handling – we just pattern match on the result of the function.

In addition to being polymorphic, new types may also be recursively defined. We can use this functionality to define our own lists, just like the built-in lists in OCaml but without the special notation:

OCaml

# type 'a sequence = Nil | Cons of 'a * 'a sequence;;
type 'a sequence = Nil | Cons of 'a * 'a sequence

We have called our type sequence to avoid confusion. It has two constructors: Nil which is equivalent to [], and Cons which is equivalent to the :: operator. Cons carries two pieces of data with it – one of type α (the head) and one of type α sequence (the tail). This is the recursive part of our definition. Now we can make our own lists equivalent to OCaml’s built-in ones:

image

Now you can see why getting at the last element of a list in OCaml is harder than getting at the first element – it is deeper in the structure. Let us compare some functions on OCaml lists with the same ones on our new sequence type. First, the ones for built-in lists.

image

And now the same functions with our new sequence type:

image

Notice how all the conveniences of pattern matching such as completeness detection and the use of the underscore work for our own types too.

A Type for Mathematical Expressions

Our sequence was an example of a recursively-defined type, which can be processed naturally by recursive functions. Mathematical expressions can be modeled in the same way. For example, the expression 1 + 2 × 3 could be drawn like this:

image

Notice that, in this representation, we never need parentheses – the diagram is unambiguous. We can evaluate the expression by reducing each part in turn:

image

Here’s a suitable type for such expressions:

image

For example, the expression 1 + 2 * 3 is represented in this data type as:

Add (Num 1, Multiply (Num 2, Num 3))

We can now write a function to evaluate expressions:

image

Building our own types leads to clearer programs with more predictable behaviour, and helps us to think about a problem – often the functions are easy to write once we have decided on appropriate types.

Questions

  1. Design a new type rect for representing rectangles. Treat squares as a special case.

  2. Now write a function of type rect int to calculate the area of a given rect.

  3. Write a function which rotates a rect such that it is at least as tall as it is wide.

  4. Use this function to write one which, given a rect list, returns another such list which has the smallest total width and whose members are sorted narrowest first.

  5. Write take, drop, and map functions for the sequence type.

  6. Extend the expr type and the evaluate function to allow raising a number to a power.

  7. Use the option type to deal with the problem that Division_by_zero may be raised from the evaluate function.

Growing Trees

We have used lists to represent collections of elements of like type but varying length, and tuples to represent collections of things of any type but fixed length. Another common type is the binary tree, which is used to represent structures which branch, such as the arithmetical expressions we constructed in the last chapter.

How can we represent such trees using an OCaml type? When we built our version of the OCaml list type, we had two constructors – Cons to hold a head and a tail, and Nil to represent the end of the list. With a tree, we need a version of Cons which can hold two tails – the left and right, and we still need a version of Nil.

image

Our type is called tree, and is polymorphic (can hold any kind of data at the branches). There are two constructors: Br for branches, which hold three things in a tuple: an element, the left sub-tree, and the right sub-tree. If it is not a Br, it is a Lf (leaf), which is used to signal that there is no left, or no right sub-tree. Here are some representations in our new type of integer trees:

image

The empty tree is simply Lf. You can see now why we used abbreviated constructor names – even small trees result in long textual representations. Let us write some simple functions on trees. To calculate the number of elements in the tree, we just count one for each branch, and zero for each leaf:

image

Notice that the recursive function follows the shape of the recursive type. A similar function can be used to add up all the integers in an int tree:

image

How can we calculate the maximum depth of a tree? The depth is the longest path from the root (top) of the tree to a leaf.

image

We defined a function max which returns the larger of two integers. Then, in our main function, we count a leaf as zero depth, and calculate the depth of a branch as one plus the maximum of the left and right sub-trees coming from that branch. Now consider extracting all of the elements from a tree into a list:

image

Notice that we chose to put all the elements on the left branch before the current element, and all the elements in the right branch after. This is arbitrary (it is clear that there are multiple answers to the question “How can I extract all the elements from a tree as a list?”). Before we consider real applications of trees, let us look at one more function. Here is how to map over trees:

image

Notice the similarity to our map function for lists, both in the type and the definition.

Using trees to build better dictionaries

We have seen that arithmetic expressions can be drawn as trees on paper, and we have designed an OCaml data type for binary trees to hold any kind of element. Now it is time to introduce the most important application of trees: the binary search tree, which is another way of implementing the dictionary data structure we described in Chapter 7.

The most important advantage of a tree is that it is often very much easier to reach a given element. When searching in a dictionary defined as a list, it took on average time proportional to the number of items in the dictionary to find a value for a key (the position of the required entry is, on average, halfway along the list). If we use a binary tree, and if it is reasonably nicely balanced in shape, that time can be reduced to the logarithm base two of the number of elements in the dictionary. Can you see why?

We can use our existing tree type. In the case of a dictionary, it will have type (α × β) tree, in other words a tree of key-value pairs where the keys have some type α and the values some type β. For this example, we are going to be using another built-in type, string. A string is a sequence of characters written between double quotation marks. We have seen these as messages attached to exceptions, but they are a basic OCaml type too.

So, our tree representing a dictionary mapping integers like 1 to their spellings like “one” would have type (int × string) tree:

image

which would be written as

Br ((3, "three"), Br ((1, "one"), Lf, Br ((2, "two"), Lf, Lf)), Br ((4, "four"), Lf, Lf))

If we arrange the tree such that, at each branch, everything to the left has a key less than the key at the branch, and everything to the right has a key greater than that at the branch, we have a binary search tree.

Lookup is simple: start at the top, and if we have not found the key we are looking for, go left or right depending upon whether the required key is smaller or larger than the value at the current branch. If we reach a leaf, the key was not in the tree (assuming the tree is a well-formed binary search tree), and we raise an exception.

image

Alternatively, we may use the option type to avoid exceptions:

image

How can we insert a new key-value pair into an existing tree? We can find the position to insert by using the same procedure as the lookup function – going left or right at each branch as appropriate. If we find an equal key, we put our new value there instead. Otherwise, we will end up at a leaf, and this is the insertion point – thus, if the key is not in the dictionary when insert is used, it will be added in place of an existing leaf.

image

For example, if we wish to insert the value "zero" for the key 0 in the tree drawn above, we would obtain

image

The shape of the tree is dependent upon the order of insertions into the tree – if they are in order (or reverse order), we obtain a rather inefficient tree – no better a dictionary than a list in fact. However, on average, we obtain a reasonably balanced tree, and logarithmic lookup and insertion times.

Lists and trees are examples of data structures. The design of an algorithm and its data structures are intimately connected.

Questions

  1. Write a function of type α α tree bool to determine if a given element is in a tree.

  2. Write a function which flips a tree left to right such that, if it were drawn on paper, it would appear to be a mirror image.

  3. Write a function to determine if two trees have the same shape, irrespective of the actual values of the elements.

  4. Write a function tree_of_list which builds a tree representation of a dictionary from a list representation of a dictionary.

  5. Write a function to combine two dictionaries represented as trees into one. In the case of clashing keys, prefer the value from the first dictionary.

  6. Can you define a type for trees which, instead of branching exactly two ways each time, can branch zero or more ways, possibly different at each branch? Write simple functions like size, total, and map using your new type of tree.

In and Out

We have considered a function (and indeed, a whole program composed of many functions) to take a chunk of data, do some calculations, and then produce a result. This assumption has allowed us to write neat, easily understood programs.

However, some computer programs do not have all data available at the beginning of the program (or even the beginning of a given function). The user might provide new data interactively, or the program might fetch data from the internet, or two or more programs might communicate with one another in real time.

We must learn how to write such programs, whilst understanding the utility of restricting such complications to as small a part of the program as possible – interactivity turns out to be surprisingly hard to reason about, since the result of a function may no longer depend only on its initial argument.

Writing to the screen

OCaml has a built-in function print_int which prints an integer to the screen:

OCaml

# print_int 100;;
100- : unit = ()

What is the type of this function? Well, it is a function, and it takes an integer as its argument. It prints the integer to the screen, and then returns…what? Nothing! OCaml has a special type to represent nothing, called unit. There is exactly one thing of type unit which is written () and is called “unit”. So, the function print_int has type int unit.

There is another built-in function print_string of type string unit to print a string, and another print_newline to move to the next line. This function has type unit unit because it requires no substantive argument and produces no useful result. It is only wanted for its “side-effect”.

We can produce several side-effects, one after another, using the ; symbol. This evaluates the expression on its left hand side, throws away the result (which will normally be unit anyway), and then evaluates the expression to its right hand side, returning the result (which is often unit too). The type of the expression x ; y is thus the type of y. For example, we can write a function to write to the screen an int × string pair as an integer on one line, followed by a string on another:

image

Notice we have added a second call to print_newline, so that our function can be called several times in a row without intervening calls to print_newline. We wrote the function applications all on one line to emphasize that ; behaves a little like an operator. However, for convenience, we would normally write it like this:

image

This makes it look rather like ; is used to end each expression, but just remember that ; is a bit like an operator – notice that there is no ; after the last print_newline (). Let us see how print_dict_entry is used in practice:

OCaml

# print_dict_entry (1, "one");;
1
one
- : unit = ()

How might we print a whole dictionary (represented as a list of entries) this way? Well, we could write our own function to iterate over all the entries:

image

Better, we can extract this method into a more general one, for doing an action on each element of a list:

image

Normally β will be unit. Now we can redefine print_dict using iter:

image

For example:

OCaml

# print_dict [(1, "one"); (2, "two"); (3, "three")];;
1
one
2
two
3
three
- : unit = ()

Reading from the keyboard

Now we should like to write a function to read a dictionary as an (int × string) list. We will use two built-in OCaml functions. The function read_int of type unit int waits for the user to type in an integer and press the Enter key. The integer is then returned. The function read_line of type unit string waits for the user to type any string and press the enter key, returning the string.

We want the user to enter a series of keys and values (integers and strings), one per line. They will enter zero for the integer to indicate no more input. Our function will take no argument, and return a dictionary of integers and strings, so its type will be unit (int × string) list.

image

We can run this function and type in some suitable values:

OCaml

# read_dict ();;
1
oak
2
ash
3
elm
0
- : (int * string) list = [(1, "oak"); (2, "ash"); (3, "elm")]

But there is a problem. What happens if we type in something which is not an integer when an integer is expected?

OCaml

# read_dict ();;
1
oak
ash
Exception: Failure "int_of_string".

We must handle this exception, and ask the user to try again. Here’s a revised function:

image

Now, typing mistakes can be fixed interactively:

OCaml

# read_dict ();;
1
oak
ash
This is not a valid integer. Please try again.
2
ash
3
elm
0
- : (int * string) list = [(1, "oak"); (2, "ash"); (3, "elm")]

Using files

It is inconvenient to have to type new data sets in each time, so we will write functions to store a dictionary to a file, and then to read it back out again.

OCaml has some basic functions to help us read and write from places data can be stored, such as files. Places we can read from have type in_channel and places we can write to have type out_channel. Here are functions for writing a dictionary of type (int × string) to a channel:

image

We are using the functions output_string and output_char to write the data in the same format we used to print it to the screen. There is no output_int function, so we have used the built-in string_of_int function to build a string from the integer. The character ’\n’ is a special one, representing moving to the next line (there is no output_newline function).

How do we obtain such a channel? The function open_out gives an output channel for filename given as a string. It has type string out_channel. After we have written the contents to the file, we must call close_out (which has type out_channel unit) to properly close the file.

image

After running this function, you should find a file of the chosen name on your computer in the same folder from which you are running OCaml. If you are not sure where the file is being put, consult the documentation for your OCaml implementation, or use a full file path such as "C:/file.txt" or "/home/yourname/file.txt", again depending on your system. In the following example, we are reading a dictionary from the user and writing it to file as file.txt:

OCaml

# dictionary_to_file "file.txt" (read_dict ());;
1
oak
2
ash
3
elm
0
- : unit

Now we have written a file, we can read it back in:

image

We have written a function entry_of_channel to read a single integer and string (one element of our dictionary) from an input channel using the built-in functions input_line and int_of_string, and a function dictionary_of_channel to read all of them as a dictionary. It makes use of the built-in exception End_of_file to detect when there is no more in the file. Now, we can build the main function to read our dictionary from the file:

image

The process is the same as for dictionary_to_file but we use open_in and close_in instead of open_out and close_out.

OCaml

# dictionary_of_file "file.txt";;
- : (int * string) list = [(1, "oak"); (2, "ash"); (3, "elm")]

Summary of functions

We have introduced the types unit, in_channel, and out_channel, and the exception End_of_file. Here are the functions we have used:

image

Questions

  1. Write a function to print a list of integers to the screen in the same format OCaml uses – i.e. with square brackets and semicolons.

  2. Write a function to read three integers from the user, and return them as a tuple. What exceptions could be raised in the process? Handle them appropriately.

  3. In our read_dict function, we waited for the user to type 0 to indicate no more data. This is clumsy. Implement a new read_dict function with a nicer system. Be careful to deal with possible exceptions which may be raised.

  4. Write a function which, given a number x, prints the x-times table to a given file name. For example, table "table.txt" 5 should produce a file table.txt containing the following:

    image

    Adding the special tabulation character \t after each number will line up the columns.

  5. Write a function to count the number of lines in a given file.

  6. Write a function copy_file of type string string unit which copies a file line by line. For example, copy_file "a.txt" "b.txt" should produce a file b.txt identical to a.txt. Make sure you deal with the case where the file a.txt cannot be found, or where b.txt cannot be created or filled.

Putting Things in Boxes

So far, we have considered “pure” functions which have no side-effects, and functions which have the side-effect of reading or writing information to and from, for example, files. When we assigned a value to a name, that value could never change. Sometimes, it is convenient to allow the value of a name to be changed – some algorithms are more naturally expressed in this way.

OCaml provides a construct known as a reference which is a box in which we can store a value. We build a reference using the built-in function ref of type α α ref. For example, let us build a reference with initial contents 0. It will have type int ref.

OCaml

# let x = ref 0;;
val x : int ref = {contents = 0}

OCaml tells us that x is a reference of type int ref which currently has contents 0. We can extract the current contents of a reference using the ! operator, which has type α ref α.

# let p = !x;;
val p : int = 0

We can update the contents of the reference using the :=  operator:

# x := 50;;
- : unit = ()

The :=  operator has type α ref α unit, since it takes a reference and a new value to put in it, puts the value in, and returns nothing. It is only useful for its side-effect. Now, we can get the contents with ! again.

# let q = !x;;
val q : int = 50
# p;;
- : int = 0

Notice that p is unchanged. Here’s a function to swap the contents of two references:

image

We needed to use a temporary name t to store the contents of a. Can you see why?

This type of programming, which consists of issuing a number of commands, in order, about which references are to be altered and how, is known as imperative programming. OCaml provides some useful structures for imperative programming with references. We will look at these quickly now, and in a moment build a bigger example program to show why they are useful.

For readability, OCaml lets us miss out the else part of the if  then  else  construct if it would just be (), which is if we are doing nothing in the else case, so

if x = 0 then a := 0 else ()

can be written as

if x = 0 then a := 0

and if x is not zero, the expression will just evaluate to (). Due to this, when putting imperative code inside if  then  else  constructs, we need to surround the inner imperative expressions with parentheses so the meaning is unambiguous:

image

OCaml allows us to use begin and end instead, for readability:

image

Doing it again and again

There are two ways to repeat an action. To perform an action a fixed number of times, we use the for … = … to … do … done construct. For example,

for x = 1 to 5 do print_int x; print_newline () done

evaluates the expression print_int x; print_newline () five times: once where x is 1, once where x is 2 etc, so the result is:

# for x = 1 to 5 do print_int x; print_newline () done;
1
2
3
4
5
- : unit = ()

This is known as a “for loop”. Note that the type of the whole for … = … to … do … done expression is unit irrespective of the type of the expression(s) inside it.

There is another looping construct – this time for evaluating an expression repeatedly until some condition is true. This is the while … do … done construct. It takes a boolean condition, and evaluates a given expression repeatedly, zero or more times, until the boolean condition is false. For example, here is a function which, given a positive integer, calculates the lowest power of two greater than or equal to that number (i.e. for the argument 37, the result will be 64).

image

The while loop continues until the contents of the reference t is greater than or equal to x. At that point, it ends, and the contents of t is returned from the function. Again, note that the type of the whole while … do … done construct is unit.

Example: text file statistics

We are going to write a program to count the number of words, sentences and lines in a text file. We shall consider the opening paragraph of Kafka’s “Metamorphosis”.

image

There are newline characters at the end of each line, save for the last. You can cut and paste or type this into a text file to try these examples out. Here, it is saved as gregor.txt.

We will just count lines first. To this, we will write a function channel_statistics to gather the statistics by reading an input channel and printing them. Then we will have a function to open a named file, call our first function, and close it again.

image

Notice the use of true as the condition for the while construct. This means the computation would carry on forever, except that the End_of_file exception must eventually be raised. Note also that OCaml emits a warning when reading the channel_statistics function:

Warning 26: unused variable line.

This is an example of a warning we can ignore – we are not using the actual value line yet, since we are just counting lines without looking at their content. Running our program on the example file gives this:

OCaml

# file_statistics "gregor.txt";;
There were 8 lines.
- : unit = ()

Let us update the program to count the number of words, characters, and sentences. We will do this simplistically, assuming that the number of words can be counted by counting the number of spaces, and that the sentences can be counted by noting instances of ’.’, ’!’, and ’?’. We can extend the channel_statistics function appropriately – file_statistics need not change:

image

We have used the built-in function String.iter of type (char unit) string unit which calls a function we supply on each character of a string.

Substituting this version of channel_statistics (if you are cutting and pasting into OCaml, be sure to also paste file_statistics in again afterwards, so it uses the new channel_statistics), gives the following result on our example text:

OCaml

# file_statistics "gregor.txt";;
There were 8 lines, making up 464 characters with 80 words in 4 sentences.
- : unit = ()

Adding character counts

We should like to build a histogram, counting the number of times each letter of the alphabet or other character occurs. It would be tedious and unwieldy to hold a hundred or so references, and then pattern match on each possible character to increment the right one. OCaml provides a data type called array for situations like this.

An array is a place for storing a fixed number of elements of like type. We can introduce arrays by using [| and |], with semicolons to separate the elements:

OCaml

# let a = [|1; 2; 3; 4; 5|];;
val a : int array = [|1; 2; 3; 4; 5|]

We can access an element inside our array in constant time by giving the position of the element (known as the subscript) in parentheses, after the array name and a period:

# a.(0);;
- : int = 1

Notice that the first element has subscript 0, not 1. We can update any of the values in the array, also in constant time, like this:

# a.(4) <- 100;;
- : unit = ()
# a;;
a : int array = [|1; 2; 3; 4; 100|]

If we try to access or update an element which is not within range, an exception is raised:

# a.(5);;
Exception: Invalid_argument "index out of bounds".

There are some useful built-in functions for dealing with arrays. The function Array.length of type α array int returns the length of an array:

# Array.length a;;
- : int = 5

In contrast to finding the length of a list, the time taken by Array.length is constant, since it was fixed when the array was created. The Array.make function is used for building an array of a given length, initialized with given values. It takes two arguments – the length, and the initial value to be given to every element. It has type int α α array.

# Array.make 6 true;;
- : bool array = [|true; true; true; true; true; true|]
# Array.make 10 'A';;
- : char array = [|'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'|]
# Array.make 3 (Array.make 3 5);;
- : int array array = [|[|5; 5; 5|]; [|5; 5; 5|]; [|5; 5; 5|]|]

Back to our original problem. We want to store a count for each possible character. We cannot subscript our arrays with characters directly, but each character has a special integer code (its so-called “ASCII code”, a common encoding of characters as integers in use since the 1960s), and we can convert to and from these using the built-in functions int_of_char and char_of_int. For example:

OCaml

# int_of_char 'C';;
- : int = 67
# char_of_int 67;;
- : char = 'C'

The numbers go from 0 to 255 inclusive (they do not all represent printable characters, for example the newline character ’\n’ has code 10). So, we can store our histogram as an integer array of length 256.

Our main function is getting rather long, so we will write a separate one which, given the completed array prints out the frequencies. If there were no instances of a particular character, no line is printed for that character.

image

This prints lines like:

For character ’d’ (character number 100) the count is 6.

Now, we can alter our channel_statistics to create an appropriate array, and update it, once again using String.iter:

image

Here is the output on our text:

OCaml

# file_statistics "gregor.txt";;
There were 8 lines, making up 464 characters with 80 words in 4 sentences.
Character frequencies:
For character ' ' (character number 32) the count is 80.
For character ',' (character number 44) the count is 6.
For character '-' (character number 45) the count is 1.
For character '.' (character number 46) the count is 4.
For character 'G' (character number 71) the count is 1.
For character 'H' (character number 72) the count is 2.
For character 'O' (character number 79) the count is 1.
For character 'S' (character number 83) the count is 1.
For character 'T' (character number 84) the count is 1.
For character 'a' (character number 97) the count is 24.
For character 'b' (character number 98) the count is 10.
For character 'c' (character number 99) the count is 6.
For character 'd' (character number 100) the count is 25.
For character 'e' (character number 101) the count is 47.
For character 'f' (character number 102) the count is 13.
For character 'g' (character number 103) the count is 5.
For character 'h' (character number 104) the count is 22.
For character 'i' (character number 105) the count is 30.
For character 'k' (character number 107) the count is 4.
For character 'l' (character number 108) the count is 23.
For character 'm' (character number 109) the count is 15.
For character 'n' (character number 110) the count is 21.
For character 'o' (character number 111) the count is 27.
For character 'p' (character number 112) the count is 3.
For character 'r' (character number 114) the count is 20.
For character 's' (character number 115) the count is 24.
For character 't' (character number 116) the count is 21.
For character 'u' (character number 117) the count is 6.
For character 'v' (character number 118) the count is 4.
For character 'w' (character number 119) the count is 6.
For character 'y' (character number 121) the count is 10.
For character 'z' (character number 122) the count is 1.
- : unit = ()

The most common character is the space. The most common alphabetic character is ’e’.

Questions

  1. Consider the expression

    let x = ref 1 in let y = ref 2 in x := !x + !x; y := !x + !y; !x + !y

    What references have been created? What are their initial and final values after this expression has been evaluated? What is the type of this expression?

  2. What is the difference between [ref 5; ref 5] and let x = ref 5 in [x; x]?

  3. Imagine that the for … = … to … do … done construct did not exist. How might we create the same behaviour?

  4. What are the types of these expressions?

    [|1; 2; 3|]

    [|true; false; true|]

    [|[|1|]|]

    [|[1; 2; 3]; [4; 5; 6]|]

    [|1; 2; 3|].(2)

    [|1; 2; 3|].(2) <- 4

  5. Write a function to compute the sum of the elements in an integer array.

  6. Write a function to reverse the elements of an array in place (i.e. do not create a new array).

  7. Write a function table which, given an integer, builds the int array array representing the multiplication table up to that number. For example, table 5 should yield:

    image

    There is more than one way to represent this as an array of arrays; you may choose.

  8. The ASCII codes for the lower case letters ’a’’z’ are 97…122, and for the upper case letters ’A’’Z’ they are 65…90. Use the built-in functions int_of_char and char_of_int to write functions to uppercase and lowercase a character. Non-alphabetic characters should remain unaltered.

  9. Comment on the accuracy of our character, word, line, and sentence statistics in the case of our example paragraph. What about in general?

  10. Choose one of the problems you have identified, and modify our program to fix it.

The Other Numbers

The only numbers we have considered until now have been the integers. For a lot of programming tasks, they are sufficient. And, except for their limited range and the possibility of division by zero, they are easy to understand and use. However, we must now consider the real numbers.

It is clearly not possible to represent all numbers exactly – they might be irrational like π or e and have no finite representation. For most uses, a representation called floating-point is suitable, and this is how OCaml’s real numbers are stored. Not all numbers can be represented exactly, but arithmetic operations are very quick.

Floating-point numbers have type float. We can write a floating-point number by including a decimal point somewhere in it. For example 1.6 or 2.  or 386.54123. Negative floating-point numbers are preceded by the -. characters just like negative integers are preceded by the - character. Similarly, we write +. -. *. /. for the standard arithmetic operators on floating-point numbers. Exponentiation is written with the ** operator.

OCaml

# 1.5;;
- : float = 1.5
# 6.;;
- : float = 6.
# -.2.3456;;
- : float = -2.3456
# 1.0 +. 2.5 *. 3.0;;
- : float = 8.5
# 1.0 /. 1000.0;;
- : float = 0.001
# 1. /. 100000.;;
- : float = 1e-05
# 3000. ** 10.;;
- : float = 5.9049e+34
# 3.123 -. 3.;;
- : float = 0.12300000000000022

Notice an example of the limits of precision in floating-point operations in the final lines. Note also that very small or very large numbers are written using scientific notation (such as 5.9049e+34 above). We can find out the range of numbers available:

OCaml

# max_float;;
- : float = 1.79769313486231571e+308
# min_float;;
- : float = 2.22507385850720138e-308

Working with floating-point numbers requires care, and a comprehensive discussion is outside the scope of this book. These challenges exist in any programming language using the floating-point system. For example, evaluating 1. /. 0. gives the special value infinity (there is no Division_by_zero exception for floating-point operations). There are other special values such as neg_infinity and nan (“not a number”). We will leave these complications for now – just be aware that they are lurking and must be confronted when writing robust numerical programs.

A number of standard functions are provided, both for operating on floating-point numbers and for converting to and from them, some of which are listed here:

image

Let us write some functions with floating-point numbers. We will write some simple operations on vectors in two dimensions. We will represent a point as a pair of floating-point numbers of type float × float such as (2.0, 3.0). We will represent a vector as a pair of floating-point numbers too. Now we can write a function to build a vector from one point to another, one to find the length of a vector, one to offset a point by a vector, and one to scale a vector to a given length:

image

Notice that we have to be careful about division by zero, just as with integers. We have used tuples for the points because it is easier to read this way – we could have passed each floating-point number as a separate argument instead, of course.

Floating-point numbers are often essential, but must be used with caution. You will discover this when answering the questions for this chapter. Some of these questions require using the built-in functions listed in the table above.

Questions

  1. Give a function which rounds a positive floating-point number to the nearest whole number, returning another floating-point number.

  2. Write a function to find the point equidistant from two given points in two dimensions.

  3. Write a function to separate a floating-point number into its whole and fractional parts. Return them as a tuple of type float × float.

  4. Write a function star of type float unit which, given a floating-point number between zero and one, draws an asterisk to indicate the position. An argument of zero will result in an asterisk in column one, and an argument of one an asterisk in column fifty.

  5. Now write a function plot which, given a function of type float float, a range, and a step size, uses star to draw a graph. For example, assuming the existence of the name pi for π, we might see:

    image

    Here, we have plotted the sine function on the range 0…π in steps of size π/20. You can define pi by calculating 4.0 *. atan 1.0.

The OCaml Standard Library

OCaml is provided with a wide range of useful built-in functions, in addition to the ones we have already seen, called the OCaml Standard Library. These functions are divided into modules, one for each area of functionality (in the next chapter, we will learn how to write our own modules). Here are a few examples of modules in the standard library:

image

We will take the List module as an example. You can find the documentation for the OCaml Standard Library installed with your copy of OCaml, or on the internet.

The functions from a module can be used by putting a period (full stop) between the module name and the function. For example the length function in the List module can be used like this:

OCaml

# List.length [1; 2; 3; 4; 5];;
- : int = 5

We can look at the type too by writing just the name of the function:

OCaml

# List.length;;
- : 'a list -> int = <fun>

Here’s the documentation for List.length:

image

We will talk about val in the next chapter. Sometimes, more information is required:

image

For example,

OCaml

# List.nth [1; 2; 4; 8; 16] 3;;
- : int = 8

In the documentation, we are told what the function does for each argument, and what exceptions may be raised. Functions which are not tail-recursive and so may fail on huge arguments are marked as such.

The questions for this chapter use functions from the standard library, so you will need to have a copy of the documentation to hand.

Questions

  1. Write your own version of the function List.concat. The implementation OCaml provides is not tail-recursive. Can you write one which is?

  2. Use List.mem to write a function which returns true only if every list in a bool list list contains true somewhere in it.

  3. Write a function to count the number of exclamation marks in a string, using one or more functions from the String module.

  4. Use the String.map function to write a function to return a new copy of a string with all exclamation marks replaced with periods (full stops).

  5. Use the String module to write a function which concatenates a list of strings together.

  6. Do the same with the Buffer module. This will be faster.

  7. Use the String module to count the number of occurrences of the string "OCaml" within a given string.

Building Bigger Programs

So far we have been writing little programs and testing them interactively in OCaml. However, to conquer the complexity of the task of writing larger programs, tools are needed to split them into well-defined modules, each with a given set of types and functions. We can then build big systems without worrying that some internal change to a single module will affect the whole program. This process of modularization is known as abstraction, and is fundamental to writing large programs, a discipline sometimes called software engineering.

In this chapter, you will have to create text files and type commands into the command prompt of your computer. If you are not sure how to do this, or the examples in this chapter do not work for you, ask a friend or teacher. In particular, if using Microsoft Windows, some of the commands may have different names.

Making a module

We will be building a modular version of our text statistics program from Chapter 13. First, write the text file shown in below (but not the italic annotations) and save it as textstat.ml (OCaml programs live in files with lowercase names ending in .ml).

image

The first line is a comment. Comments in OCaml are written between (* and *). We use comments in large programs to help the reader (who might be someone else, or ourselves some time later) to understand the program.

We have then introduced a type for our statistics. This will hold the number of words, characters, and sentences. We have then written a function stats_from_channel which for now just returns zeros for all the statistics.

Now, we can issue a command to turn this program into a pre-processed OCaml module. This compiles the program into an executable. The module can then be loaded into interactive OCaml, or used to build standalone programs. Execute the following command:

ocamlc textstat.ml

You can see that the name of the OCaml compiler is ocamlc. If there are errors in textstat.ml they will be printed out, including the line and character number of the problem. You must fix these, and try the command again. If compilation succeeds, you will see the file textstat.cmo in the current directory. There will be other files, but we are not worried about those yet. Let us load our pre-compiled module into OCaml:

OCaml

# #load "textstat.cmo";;
# Textstat.stats_from_file "gregor.txt";;
- : int * int * int * int = (0, 0, 0, 0)

Note that #load is different from our earlier #use command – that was just reading a file as if it had been cut and pasted – we are really loading the compiled module here.

Filling out the module

Let us add a real stats_from_channel function, to produce a working text statistics module. We will also add utility functions for retrieving individual statistics from the stats type:

image

We can compile it in the same way, and try it with our example file:

OCaml

# #load "textstat.cmo";;
# let s = Textstat.stats_from_file "gregor.txt";;
val s : Textstat.stats = (8, 464, 80, 4)
# Textstat.lines s;;
- : int = 8
# Textstat.characters s;;
- : int = 464
# Textstat.words s;;
- : int = 80
# Textstat.sentences s;;
- : int = 4

You might ask why we need the functions lines, characters etc. when the information is returned in the tuple. Let us discuss that now.

Making an interface

We said that modules were for creating abstractions, so that the implementation of an individual module could be altered without changing the rest of the program. However, we have not achieved that yet – the details of the internal type are visible to the program using the module, and that program would break if we changed the type of stats to hold an additional statistic. In addition, the internal count_words function is available, even though the user of the module is not expected to use it.

What we would like to do is to restrict the module so that only the types and functions we want to be used directly are available. For this, we use an interface. Interfaces are held in files ending in .mli, and we can write one for our module. Here is our interface:

image

In this interface, we have exposed every type and function. Types are written in the same way as in the .ml file. Functions are written with val, followed by the name, a colon, and the type of the function. We can compile this by giving the .mli file together with the .ml file when using ocamlc:

ocamlc textstat.mli textstat.ml

The ocamlc compiler has created at least two files: textstat.cmo as before and textstat.cmi (the compiled interface). You should find this operates exactly as before when loaded into OCaml. Now, let us remove the definition of the type from the interface, to make sure that the stats type is hidden, and its parts can only be accessed using the lines, characters, words, and sentences functions. We will also remove the declaration for stats_from_channel to demonstrate that functions we do not need can be hidden too:

image

Now, if we compile the program again with ocamlc textstat.mli textstat.ml, we see that the stats_of_channel function is now not accessible, and the type of stats is now hidden, or abstract.

OCaml

# #load "textstat.cmo";;
# let s = Textstat.stats_from_file "gregor.txt";;
val s : Textstat.stats = <abstr>
# Textstat.lines s;;
- : int = 8
# Textstat.characters s;;
- : int = 464
# Textstat.words s;;
- : int = 80
# Textstat.sentences s;;
- : int = 4
# Textstat.stats_from_channel;;
Error: Unbound value Textstat.stats_from_channel

We have successfully separated the implementation of our module from its interface – we can now change the stats type internally to hold extra statistics without invalidating existing programs. This is abstraction in a nutshell.

Building standalone programs

Now it is time to cut ourselves free from interactive OCaml, and build standalone programs which can be executed directly. Let us add another file stats.ml which will use functions from the Textstat module to create a program which, when given a file name, prints some statistics about it:

image

There are some new things here:

  1. The built-in array Sys.argv lists the arguments given to a command written at the command line. The first is the name of our program, so we ignore that. The second will be the name of the file the user wants our program to inspect. So, we match against that array. If there is any other array size, we print out a usage message.

  2. The function Printexc.to_string from the OCaml Standard Library converts an exception into a string – we use this to print out the error.

  3. There was an error, so it is convention to specify an exit code of 1 rather than 0. Do not worry about this.

Let us compile this standalone program using ocamlc, giving a name for the executable program using the -o option:

ocamlc textstat.mli textstat.ml stats.ml -o stats

Now, we can run the program:

$ ./stats gregor.txt
Words: 80
Characters: 464
Sentences: 4
Lines: 8
$ ./stats not_there.txt
An error occurred: Sys_error("not_there.txt: No such file or directory")
$ ./stats
Usage: stats <filename>

This output might look different on your computer, depending on your operating system. On most computers, the ocamlopt compiler is also available. If we type

ocamlopt textstat.mli textstat.ml stats.ml -o stats

we obtain an executable which is much faster than before, and completely independent of OCaml – it can run on any computer which has the same processor and operating system (such as Windows or Mac OS X) as yours, with no need for an OCaml installation. On the other hand, the advantage of ocamlc is that it produces a program which can run on any computer, so long as OCaml support is installed.

Questions

  1. Extend our example to print the character histogram data as we did in Chapter 13.

  2. Write and compile a standalone program to reverse the lines in a text file, writing to another file.

  3. Write a program which takes sufficiently long to run to allow you to compare the speed of programs compiled with ocamlc and ocamlopt.

  4. Write a standalone program to search for a given string in a file. Lines where the string is found should be printed to the screen.

Answers to Questions

1. Starting Off

1

The expression 17 is of type int and is a value already. The expression 1 + 2 * 3 + 4 is of type int and evaluates to the value 11, since the multiplication is done first. The expression 800 / 80 / 8 has type int. It is the same as (800 / 80) / 8 rather than 800 / (80 / 8) and evaluates to 1.

The expression 400 > 200 has type bool because this is the type of the result of the comparison operator >. It evaluates to true. Similarly, 1 <> 1 has type bool and evaluates to false. The expression true || false is of type bool and evaluates to true since one of the operands is true. Similarly, true && false evaluates to false since one of the operands is false. The expression  if true then false else true  evaluates to false since the first (then) part of the conditional expression is chosen, and takes the place of the entire expression.

The expression ’%’ is of type char and is already a value. The expression ’a’ + ’b’ has no type – it gives a type error because the + operator does not operate on characters.

2

The mod operator is of higher precedence than the + operator. So 1 + 2 mod 3 and 1 + (2 mod 3) are the same expression, evaluating to 1 + 2 which is 3, but (1 + 2) mod 3 is the same as 3 mod 3, which is 0.

3

The expression evaluates to 11. The programmer seems to be under the impression that spacing affects evaluation order. It does not, and so this use of space is misleading.

4

The expression max_int + 1 evaluates to a number equal to min_int. Likewise, min_int - 1 evaluates to a number equal to max_int. The number line “wraps around”. This leads to the odd situation that max_int + 1 < max_int evaluates to true. It follows that when writing programs, we must be careful about what happens when numbers may be very large or very small.

5

OCaml accepts the program, but complains when it is run:

OCaml

# 1 / 0;;
Exception: Division_by_zero.

We will talk about such exceptions later in the book. They are used for program errors which cannot necessarily be found just by looking at the program text, but are only discovered during evaluation.

6

For x mod y:

This illustrates how even simple mathematical operators require careful specification when programming – we must be explicit about the rules.

7

It prevents unexpected values: what would happen if an integer other than 1 and 0 was calculated in the program – what would it mean? It is better just to use a different type. We can then show more easily that a program is correct.

8

The lowercase characters are in alphabetical order, for example ’p’ < ’q’ evaluates to true. The uppercase characters are similarly ordered. The uppercase letters are all “smaller” than the lowercase characters, so for example ’A’ < ’a’ evaluates to true. For type bool, false is considered “less than” true.

2. Names and Functions

1

Just take in an integer and return the number multiplied by ten. The function takes and returns an integer, so the type is int int.

OCaml

# let times_ten x = x * 10;;
val times_ten : int -> int = <fun>

2

We must take two integer arguments, and use the && and <> operators to test if they are both non-zero. So the result will be of type bool. The whole type will therefore be  int int bool.

OCaml

# let both_non_zero x y =
x <> 0 && y <> 0;;
val both_non_zero : int -> int -> bool = <fun>

3

Our function should take an integer, and return another one (the sum). So, it is type will be int int. The base case is when the number is equal to 1. Then, the sum of all numbers from 1…1 is just 1. If not, we add the argument to the sum of all the numbers from 1…(n−1).

OCaml

# let rec sum n =
if n = 1 then 1 else n + sum (n - 1);;
val sum : int -> int = <fun>

The function is recursive, so we used let rec instead of let. What happens if the argument given is zero or negative?

4

The function will have type int int int. A number to the power of 0 is 1. A number to the power of 1 is itself. Otherwise, the answer is the current n multiplied by nx − 1.

OCaml

# let rec power x n =
if n = 0 then 1 else
(if n = 1 then x else
x * power x (n - 1));;
val power : int -> int -> int = <fun>

Notice that we had to put one if … then … else inside the else part of another to cope with the three different cases. The parentheses are not actually required, though, so we may write it like this:

OCaml

# let rec power x n =
if n = 0 then 1 else
if n = 1 then x else
x * power x (n - 1);;
val power : int -> int -> int = <fun>

In fact, we can remove the case for n = 1 since power x 1 will reduce to x * power x 0 which is just x.

5

The function isconsonant will have type char bool. If a lower case character in the range ’a’’z’ is not a vowel, it must be a consonant. So we can reuse the isvowel function we wrote earlier, and negate its result using the not function:

OCaml

# let isconsonant c = not (isvowel c);;
val isconsonant : char -> bool = <fun>

6

The expression is the same as let x = 1 in (let x = 2 in x + x), and so the result is 4. Both instances of x in x + x evaluate to 2 since this is the value assigned to the name x in the nearest enclosing let expression.

7

We could simply return 0 for a negative argument. The factorial of 0 is 1, so we can change that too, and say our new function finds the factorial of any non-negative number:

OCaml

# let rec factorial x =
if x < 0 then 0 else
if x = 0 then 1 else
x * factorial (x - 1);;
val factorial : int -> int = <fun>

Note that factorial can fail in other ways too – if the number gets too big and “wraps around”. For example, on the author’s computer, factorial 40 yields -2188836759280812032.

3. Case by Case

1

We can just pattern match on the boolean. It does not matter, in this instance, which order the two cases are in.

image

2

Recall our solution from the previous chapter:

image

Modifying it to use pattern matching:

image

3

Again, modifying our solution from the previous chapter:

image

5

This is the same as

image

(A match case belongs to its nearest enclosing match). So the expression evaluates to 5.

6

We write two functions of type char bool like this:

image

Alternatively, we might write:

image

These two solutions have differing behaviour upon erroneous arguments (such as punctuation). Can you see why?

4. Listing Things

1

This is similar to odd_elements:

image

But we can perform the same trick as before, by reversing the cases, to reduce their number:

image

This is like counting the length of a list, but we only count if the current element is true.

image

We can use an accumulating argument in an auxiliary function to make a tail recursive version:

image

3

To make a palindrome from any list, we can append it to its reverse. To check if a list is a palindrome, we can compare it for equality with its reverse (the comparison operators work over almost all types).

image

4

We pattern match with three cases. The empty list, where we have reached the last element, and where we have yet to reach it.

image

We can build a tail recursive version by adding an accumulating list, and reversing it when finished (assuming a tail recursive rev, of course!)

image

5

The empty list cannot contain the element; if there is a non-empty list, either the head is equal to the element we are looking for, or if not, the result of our function is just the same as the result of recursing on the tail.

Note that we are using the property that the || operator only evaluates its right hand side if the left hand side is false to limit the recursion – it really does stop as soon as it finds the element.

image

6

If a list is empty, it is already a set. If not, either the head exists somewhere in the tail or it does not; if it does exist in the tail, we can discard it, since it will be included later. If not, we must include it.

image

For example, consider the evaluation of make_set [4; 5; 6; 5; 4]:

image

7

The first part of the evaluation of rev takes time proportional to the length of the list, processing each element once. However, when the lists are appended together, the order of the operations is such that the first argument becomes longer each time. The @ operator, as we know, also takes time proportional to the length of its first argument. And so, this accumulating of the lists takes time proportional to the square of the length of the list.

image

By using an additional accumulating argument, we can write a version which operates in time proportional to the length of the list.

image

For the same list:

image

5. Sorting Things

1

Simply add an extra let to define a name representing the number we will take or drop:

image

2

The argument to take or drop is length l / 2 which is clearly less than or equal to length l for all possible values of l. Thus, take and drop always succeed. In our case, take and drop are only called with length l more than 1, due to the pattern matching.

3

We may simply replace the <= operator with the >= operator in the insert function.

image

The sort function is unaltered.

4

We require a function of type α list bool. List of length zero and one are, by definition, sorted. If the list is longer, check that its first two elements are in sorted order. If this is true, also check that the rest of the list is sorted, starting with the second element.

image

We can reverse the cases to simplify:

image

5

Lists are compared starting with their first elements. If the elements differ, they are compared, and that is the result of the comparison. If both have the same first element, the second elements are considered, and so on. If the end of one list is reached before the other, the shorter list is considered smaller. For example:

[1] < [2] < [2; 1] < [2; 2]

These are the same principles you use to look up a word in a dictionary: compare the first letters – if same, compare the second etc. So, when applied to the example in the question, it has the effect of sorting the words into alphabetical order.

6

The let rec construct can be nested just like the let construct:

image

We have renamed the second argument of the insert function to avoid confusion.

Chapter 6. Functions upon Functions upon Functions

1

Our function will have type char list char list. We just match on the argument list: if it is empty, we are done. If it starts with an exclamation mark, we output a period, and carry on. If not, we output the character unchanged, and carry on:

image

To use map instead, we write a simple function calm_char to process a single character. We can then use map to build our main function:

image

This avoids the explicit recursion of the original, and so it is easier to see what is going on.

2

The clip function is of type int int and is easy to write:

image

Now we can use map for the cliplist function:

image

3

Just put the body of the clip function inside an anonymous function:

image

4

We require a function apply f n x which applies function f a total of n times to the initial value x. The base case is when n is zero.

image

Consider the type:

image

The function f must take and return the same type α, since its result in one iteration is fed back in as its argument in the next. Therefore, the argument x and the final result must also have type α. For example, for α = int, we might have a power function:

image

So power a b calculates ab.

5

We can add an extra argument to the insert function, and use that instead of the comparison operator:

image

Now we just need to rewrite the sort function.

image

6

We cannot use map here, because the result list will not necessarily be the same length as the argument list. The function will have type (α bool) α list α list.

image

For example, filter (fun x -> x mod 2 = 0) [1; 2; 4; 5] evaluates to [2; 4].

7

The function will have type (α bool) α list bool.

image

For example, we can see if all elements of a list are positive: for_all (fun x -> x > 0) [1; 2; -1] evaluates to false. Notice that we are relying on the fact that && only evaluates its right hand side when the left hand side is true to limit the recursion.

8

The function will have type (α β) α list list β list list. We use map on each element of the list.

image

We have used explicit recursion to handle the outer list, and map to handle each inner list.

Chapter 7. When Things go Wrong

1

The function smallest_inner takes a currently smallest found integer, a boolean value found indicating if we have found any suitable value or not, and the list of integers. It is started with max_int as the current value, so that any number is smaller than it, and false for found because nothing has been found yet.

image

Thus, the function raises an exception in the case of an empty list, or one which is non-empty but contains no positive integer, and otherwise returns the smallest positive integer in the list.

2

We just surround the call to smallest with an exception handler for Not_found.

image

3

We write a function sqrt_inner which, given a test number x and a target number n squares x and tests if it is more than n. If it is, the answer is x - 1. The test number will be initialized at 1. The function sqrt raises our exception if the argument is less than zero, and otherwise begins the testing process.

image

4

We wrap up the function, handle the Complex exception and return.

image

Chapter 8. Looking Things Up

1

Since the keys must be unique, the number of different keys is simply the length of the list representing the dictionary – so we can just use the usual length function.

2

The type is the same as for the add function. However, if we reach the end of the list, we raise an exception, since we did not manage to find the entry to replace.

image

3

The function takes a list of keys and a list of values and returns a dictionary. So it will have type α list β list (α × β) list.

image

4

This will have the type (α × β) list α list × β list. For the first time, we need to return a pair, building up both result lists element by element. This is rather awkward, since we will need the tails of both of the eventual results, so we can attach the new heads. We can do this by pattern matching.

image

Here’s a sample evaluation (we cannot really show it in the conventional way, so you must work through it whilst looking at the function definition):

image

Since the inner pattern match has only one form, and is complete, we can use let instead:

image

5

We can use our member function which determines whether an element is a member of a list, building up a list of the keys we have already seen, and adding to the result list of key-value pairs only those with new keys.

image

How long does this take to run? Consider how long member takes.

6

We pattern match on the first list – if it is empty, the result is simply b. Otherwise, we add the first element of the first list to the union of the rest of its elements and the second list.

image

We can verify that the elements of dictionary a have precedence over the elements of dictionary b by noting that add replaces a value if the key already exists.

Chapter 9. More with Functions

1

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 or two arguments to the function g (which is called partial application), or apply all three at once. When we write let g a b c = … this is just shorthand for let g = fun a -> fun b -> fun c -> …

2

The type of member is α α list bool, so if we partially apply the first argument, the type of member x must be α list bool. We can use the partially-applied member function and map to produce a list of boolean values, one for each list in the argument, indicating whether or not that list contains the element. Then, we can use member again to make sure there are no false booleans in the list.

image

We could also write:

image

Which do you think is clearer? Why do we check for the absence of false rather than the presence of true?

3

The function ( / ) 2 resulting from the partial application of the / operator is the function which divides two by a given number, not the function which divides a given number by two. We can define a reverse divide function…

let rdiv x y = y / x

…which, when partially applied, does what we want.

4

The function map has type (α β) α list β list. The function mapl we wrote has type (α β) α list list β list list. So the function mapll will have type (α β) α list list list β list list list. It may be defined thus:

image

But, as discussed, we may remove the ls too:

image

It is not possible to write a function which would map a function f over a list, or list of lists, or list of lists of lists depending upon its argument, because every function in OCaml must have a single type. If a function could map f over an α list list it must inspect its argument enough to know it is a list of lists, thus it could not be used on a β list unless β = α list.

5

We can write a function to truncate a single list using our take function, being careful to deal with the case where there is not enough to take, and then use this and map to build truncate itself.

image

Here we have used partial application of truncate to build a suitable function for map. Note that we could use exception handling instead of calling length, saving time:

image

You might, however, reflect on whether or not this is good style.

6

First, define a function which takes the given number and a list, returning the first element (or the number if none). We can then build the main function, using partial application to make a suitable function to give to map:

image

Chapter 10. New Kinds of Data

1

We need two constructors – one for squares, which needs just a single integer (the length of a side), and one for rectangles which needs two integers (the width and height, in that order):

image

The name of our new type is rect. A rect is either a Square or a Rectangle. For example,

image

2

We pattern match on the argument:

image

3

This will be a function of type rect rect. Squares remain unaltered, but if we have a rectangle with a bigger width than height, we rotate it by ninety degrees.

image

4

We will use map to perform our rotation on any rects in the argument list which need it. We will then use the sorting function from the previous chapter which takes a custom comparison function so as to just compare the widths.

image

For example, packing the list of rects

[Square 6; Rectangle (4, 3); Rectangle (5, 6); Square 2]

will give

[Square 2; Rectangle (3, 4); Rectangle (5, 6); Square 6]

5

We follow the same pattern as for the list type, being careful to deal with exceptional circumstances:

image

6

We can use our power function from earlier:

image

7

We can just wrap up the previous function:

image

Chapter 11. Growing Trees

1

Our function will have type α α tree bool. It takes a element to look for, a tree holding that kind of element, and returns true if the element is found, or false otherwise.

image

Note that we have placed the test x = y first of the three to ensure earliest termination upon finding an appropriate element.

2

Our function will have type α tree α tree. A leaf flips to a leaf. A branch has its left and right swapped, and we must recursively flip its left and right sub-trees too.

image

3

We can check each part of both trees together. Leaves are considered equal, branches are equal if their left and right sub-trees are equal.

image

4

We can use the tree insertion operation repeatedly:

image

There will be no key clashes, because the argument should already be a dictionary. If it is not, earlier keys are preferred since insert replaces existing keys.

5

We can make list dictionaries from both tree dictionaries, append them, and build a new tree from the resultant list.

image

The combined list may not be a dictionary (because it may have repeated keys), but tree_of_list will prefer keys encountered earlier. So, we put entries from t’ after those from t.

6

We will use a list for the sub-trees of each branch, with the empty list signifying there are no more i.e. that this is the bottom of the tree. Thus, we only need a single constructor.

type ’a mtree = Branch of ’a * ’a mtree list

So, now we can define size, total, and map.

image

In fact, when there is only one pattern to match, we can put it directly in place of the function’s argument, simplifying these definitions:

image

Chapter 12. In and Out

1

A first attempt might be:

image

However, there are two problems:

OCaml

# [1; 2; 3];;
- : int list = [1; 2; 3]
# print_integers [1; 2; 3];;
[1; 2; 3; ]- : unit = ()

There is an extra space after the last element, and a semicolon too. We can fix this, at the cost of a longer program:

image

Now, the result is correct:

OCaml

# [1; 2; 3];;
- : int list = [1; 2; 3]
# print_integers [1; 2; 3];;
[1; 2; 3]- : unit = ()

2

We must deal with the exception raised when read_int attempts to read something which is not an integer, as before. When that exception is caught, we try again, by recursively calling ourselves. The function ends when three integers are input correctly, returning them as a tuple.

image

You may wonder why we used nested let …  in …  structures rather than just writing (read_int (), read_int (), read_int ()) – the evaluation order of a tuple is not specified and OCaml is free to do what it wants.

3

We ask the user how many dictionary entries will be entered, eliminating the need for a special “I have finished” code. First, a function to read a given number of integer–string pairs, dealing with the usual problem of malformed integers:

image

And now, asking the user how many entries there will be, and calling our first function:

image

Notice that we defined, raised, and handled our own exception BadNumber to deal with the user asking to read a negative number of dictionary entries – this would cause read_dict_number to fail to return.

4

If we write a function to build the list of integers from 1 to n (or the empty list if n is zero):

image

We can then write a function to output a table of a given size to an output channel.

image

Look at this carefully. We are using nested calls to iter to build the two-dimensional table from one-dimensional lists. Can you separate this into more than one function? Which approach do you think is more readable?

We can test write_table_channel most easily by using the built-in output channel stdout which just writes to the screen:

OCaml

# write_table_channel stdout 5;;
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25
- : unit = ()

Now we just need to wrap it in a function to open an output file, write the table, and close the output, dealing with any errors which may arise.

image

In addition to raising Invalid_argument in the case of a negative number, we handle all possible exceptions to do with opening, writing to, and closing the file, re-raising them as our own, predefined one. Is this good style?

5

We write a simple function to count the lines in a channel by taking a line, ignoring it, and adding one to the result of taking another line; our recursion ends when an End_of_file exception is raised – it is caught and 0 ends the summation.

The main function countlines just opens the file, calls the first function, and closes the file. Any errors are caught and re-raised using the built-in Failure exception.

image

6

As usual, let us write a function to deal with channels, and then deal with opening and closing files afterward. Our function takes an input channel and an output channel, adds the line read from the input to the output, follows it with a newline character, and continues. It only ends when the End_of_file exception is raised inside input_line and caught.

image

Now we wrap it up, remembering to open and close both files and deal with the many different errors which might occur.

image

Chapter 13. Putting Things in Boxes

1

Two references, x and y, of type int ref have been created. Their initial values are 1 and 2. Their final values are 2 and 4. The type of the expression is int because this is the type of !x + !y, and the result is 6.

2

The expression [ref 5; ref 5] is of type int ref list. It contains two references each containing the integer 5. Changing the contents of one reference will not change the contents of the other. The expression let x = ref 5 in [x; x] is also of type int ref list and also contains two references to the integer 5. However, altering one will alter the other:

OCaml

# let r = let x = ref 5 in [x; x];;
val r : int ref list = [{contents = 5}; {contents = 5}]
# match r with h::_ -> h := 6;;
Warning 8 [partial-match]: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
[]
- : unit = ()
# r;;
- : int ref list = [{contents = 6}; {contents = 6}]

3

We can write a function forloop which takes a function of type int α (where alpha would normally be unit), together with the start and end numbers:

image

For example:

OCaml

# forloop print_int 2 10;;
2345678910- : unit = ()
# forloop print_int 2 2;;
2- : unit = ()

4

[|1; 2; 3|] : int array

[|true; false; true|] : bool array

[|[|1|]|] : (int array) array which is int array array

[|[1; 2; 3]; [4; 5; 6]|] : int list array

[|1; 2; 3|].(2) : int, has value 2

[|1; 2; 3|].(2) <- 4 : unit, updates the array to [|1; 2; 4|]

5

We use a for construct:

image

Note that this works for the empty array, because a for construct where the second number is less than the first never executes its expression.

6

Since we wish to reverse the array in place, our function will have type α array unit. Our method is to proceed from the first element to the half-way point, swapping elements from either end of the array. If the array has odd length, the middle element will not be altered.

image

7

We will represent the int array array as an array of columns so that a.(x).(y) is the element in column x and row y.

image

Note that the result is correct for table 0.

8

The difference between the codes for ’a’ and ’A’, or ’z’ and ’Z’ is 32, so we add or subtract as appropriate. Codes not in those ranges are unaltered.

image

9

Periods, exclamation marks and question marks may appear in multiples, leading to a wrong answer. The number of characters does not include newlines. It is not clear how quotations would be handled. Counting the words by counting spaces is inaccurate – a line with ten words will count only nine.

Chapter 14. The Other Numbers

1

We calculate the ceiling and floor, and return the closer one, being careful to make sure that a point equally far from the ceiling and floor is rounded up.

image

The behaviour with regard to values such as infinity and nan is fine, since it always returns the result of either floor or ceil.

2

The function returns another point, and is simple arithmetic.

image

3

The whole part is calculated using the built-in floor function. We return a tuple, the first number being the whole part, the second being the original number minus the whole part. In the case of a negative number, we must be careful – floor always rounds downward, not toward zero!

image

Notice that we are using the unary operator -. to make the number positive.

4

We need to determine at which column the asterisk will be printed. It is important to make sure that the range 0…1 is split into fifty equal sized parts, which requires some careful thought. Then, we just print enough spaces to pad the line, add the asterisk, and a newline character.

image

5

image

No allowance has been made here for bad arguments (for example, b smaller than a). Can you extend our program to move the zero-point to the middle of the screen, so that the sine function can be graphed even when its result is less than zero?

Chapter 15. The OCaml Standard Library

1

A non-tail-recursive one is simple:

image

To make a tail-recursive one, we can use an accumulator, reversing each list as we append it, and reversing the result. List.rev is tail-recursive already.

image

2

We can use List.mem, partially applied, to map over the list of lists. We then make sure that false is not in the resultant list, again with List.mem.

image

3

The String.iter function calls a user-supplied function of type char unit on each character of the string. We can use this to increment a counter when an exclamation mark is found.

image

The contents of the counter is then the result of the function.

4

We can use the String.map function, which takes a user-supplied function of type char char and returns a new string, where each character is the result of the mapping function on the character in the same place in the old string.

image

Notice that we have taken advantage of partial application to erase the last argument as usual.

5

Looking at the documentation for the String module we find the following:

image

So, by using the empty string as a separator, we have what we want:

image

6

We can use the functions create, add_string, and contents from the Buffer module together with the usual list iterator List.iter:

image

The initial size of the buffer, 100, is arbitrary.

7

We repeatedly check if the string we are looking for is right at the beginning of the string to be searched. If not, we chop one character off the string to be searched, and try again. Every time we find a match, we increment a counter.

image

You might consider that writing this function with lists of characters rather than strings would be easier. Unfortunately, it would be slow, and these kinds of searching tasks are often required to be very fast.

Chapter 16. Building Bigger Programs

1

First, we extend the Textstat module to allow frequencies to be counted and expose it through the interface. Here is the interface q1.mli:

image

Here is its implementation q1.ml:

image

Here is the main program:

image

2

We can write two little functions – one to read all the lines from a file, and one to write them. The main function, then, reads the command line to find the input and output file names, reads the lines from the input, reverses the list of lines, and writes them out. If a problem occurs, the exception is printed out. If the command line is badly formed, we print a usage message and exit.

image

Note that there is a problem if the file has no final newline – it will end up with one. How might you solve that?

3

We can simply do something (or nothing) a huge number of times using a for loop.

image

On many systems, typing time followed by a space and the usual command will print out on the screen how long the program took to run. For example, on the author’s computer:

$ ocamlc bigloop.ml -o bigloop
$ time ./bigloop

real  0m1.896s
user  0m1.885s
sys   0m0.005s

$ ocamlopt bigloop.ml -o bigloop
$ time ./bigloop

real  0m0.022s
user  0m0.014s
sys   0m0.003s

You can see that, when compiled with ocamlc, it takes 1.9s to run, but when compiled with ocamlopt just 0.022s.

4

We can get all the lines in the file using our getlines function from question two. The main function simply calls string_in_line on each line, printing it if true is returned.

The interesting function is string_in_line. To see if term is in line we start at position 0. The condition for the term having been found is a combination of boolean expressions. The first ensures that we are not so far through the string that the expression could not possibly fit at the current position. The second checks to see if the term is found at the current position by using the function String.sub from the OCaml Standard Library. If not, we carry on.

image

Hints for Questions

1. Starting Off

1

Try to work these out on paper, and then check by typing them in. Remember that the type of an expression is the type of the value it will evaluate to. Can you show the steps of evaluation for each expression?

2

Type each expression in. What number does each evaluate to? Can you work out which operator (mod or +) is being calculated first?

3

Type it in. What does OCaml print? What is the evaluation order?

7

What if a value of 2 appeared? How might we interpret it?

2. Names and Functions

1

The function takes one integer, and returns that integer multiplied by ten. So what must its type be?

2

What does the function take as arguments? What is the type of its result? So what is the whole type? You can use the <> and && operators here.

3

This will be a recursive function, so remember to use let rec. What is the sum of all the integers from 1…1? Perhaps this is a good base case.

4

This will be a recursive function. What happens when you raise a number to the power 0? What about the power 1? What about a higher power?

5

Can you define this in terms of the isvowel function we have already written?

6

Try adding parentheses to the expression in a way which does not change its meaning. Does this make it easier to understand?

7

When does it not terminate? Can you add a check to see when it might happen, and return 0 instead? What is the factorial of 0 anyway?

3. Case by Case

1

We are pattern matching on a boolean value, so there are just two cases: true and false.

2

Convert the if … then … else structure of the sum function from the previous chapter into a pattern matching structure.

3

You will need three cases as before – when the power is 0, 1 or greater than 1 – but now in the form of a pattern match.

5

Consider where parentheses might be added without altering the expression.

6

There will be two cases in each function – the special range pattern x..y, and _ for any other character.

4. Listing Things

1

Consider three cases: (1) the argument list is empty, (2) the argument list has one element, (3) the argument list has more than one element a::b::t. In the last case, which element do we need to miss out?

2

The function will have type bool list int. Consider the empty list, the list with true as its head, and the list with false as its head. Count one for each true and zero for each false.

3

The function to make a palindrome is trivial; to detect if a list is a palindrome, consider the definition of a palindrome – a list which equals its own reverse.

4

Consider the cases (1) the empty list, (2) the list with one element, and (3) the list with more than one element. For the tail recursive version, use an accumulating argument.

5

Can any element exist in the empty list? If the list is not empty, it must have a head and a tail. What is the answer if the element we are looking for is equal to the head? What do we do if it is not?

6

The empty list is already a set. If we have a head and a tail, what does it tell us to find out if the head exists within the tail?

7

Consider in which order the @ operators are evaluated in the reverse function. How long does each append take? How many are there?

5. Sorting Things

1

Consider adding another let before let left and let right.

2

Consider the situations in which take and drop can fail, and what arguments msort gives them at each recursion.

3

This is a simple change – consider the comparison operator itself.

4

What will the type of the function be? Lists of length zero and one are already sorted – so these will be the base cases. What do we do when there is more than one element?

6

You can put one let rec construct inside another.

2. Names and Functions

1

The function calm is simple recursion on lists. There are three cases – the empty list, a list beginning with ’!’ and a list beginning with any other character. In the second part of the question, write a function calm_char which processes a single character. You can then use map to define a new version of calm.

2

This is the same process as Question 1.

3

Look back at the section on anonymous functions. How can clip be expressed as an anonymous function? So, how can we use it with map?

4

We want a function of the form let rec apply f n x =  … which applies f to x a total of n times. What is the base case? What do we do in that case? What otherwise?

5

You will need to add the extra function as an argument to both insert and sort and use it in place of the <= operator in insert.

6

There are three possibilities: the argument list is empty, true is returned when its head is given to the function f, or false is returned when its head is given to the function f.

7

If the input list is empty, the result is trivially true – there cannot possibly be any elements for which the function does not hold. If not, it must hold for the first one, and for all the others by recursion.

8

You can use map on each α list in the α list list.

Chapter 7. When Things go Wrong

1

Make sure to consider the case of the empty list, where there is no smallest positive element, and also the non-empty list containing entirely zero or negative numbers.

2

Just put an exception handler around the function in the previous question.

3

First, write a function to find the number less than or equal to the square root of its argument. Now, define a suitable exception, and wrap up your function in another which, on a bad argument, raises the exception or otherwise calls your first function.

4

Use the try … with construct to call your function and handle the exception you defined.

Chapter 7. Looking Things Up

1

The keys in a dictionary are unique – does remembering that fact help you?

2

The type will be the same as for the add function, but we only replace something if we find it there – when do we know we will not find it?

3

The function takes a list of keys and a list of values, and returns a dictionary. So it will have type α list β list (α × β) list. Try matching on both lists at once – what are the cases?

4

This function takes a list of pairs and produces a pair of lists. So its type must be (α × β) list α list × β list.

For the base case (the empty dictionary), we can see that the result should be ([], []). But what to do in the case we have (k, v) :: more? We must get names for the two parts of the result of our function on more, and then cons k and v on to them – can you think of how to do that?

5

You can keep a list of the keys which have already been seen, and use the member function to make sure you do not add to the result list a key-value pair whose key has already been included.

6

The function will take two dictionaries, and return another – so you should be able to write down its type easily.

Try pattern matching on the first list – when it is empty, the answer is trivial – what about when it has a head and a tail?

Chapter 9. More with Functions

2

Try building a list of booleans, each representing the result of member on a list.

3

The / operator differs from the * operator in an important sense. What is it?

4

The type of map is (α β) α list β list. The type of mapl is (α β) α list list β list list. So, what must the type of mapll be? Now, look at our definition of mapl – how can we extend it to lists of lists of lists?

5

Use our revised take function to process a single list. You may then use map with this (partially applied) function to build the truncate function.

6

Build a function firstelt which, given the number and a list, returns the first element or that number. You can then use this function (partially applied) together with map to build the main firstelts function.

Chapter 10. New Kinds of Data

1

The type will have two constructors: one for squares, requiring only a single integer, and one for rectangles, requiring two: one for the width and one for the height.

2

The function will have type rect int. Work by pattern matching on the two constructors of your type.

3

Work by pattern matching on your type. What happens to a square. What to a rectangle?

4

First, we need to rotate the rectangles as needed – you have already written something for this. Then, we need to sort them according to width. Can you use our sort function which takes a custom comparison function for this?

5

Look at how we re-wrote length and append for the sequence type.

6

Add another constructor, and amend evaluate as necessary.

7

Handle the exception, and return None in that case.

Chapter 11. Growing Trees

1

The type will be α α tree bool. That is, it takes an element to search for, and a tree containing elements of the same type, and returns true if the element is found, and false if not. What happens if the tree is a leaf? What if it is a branch?

2

The function will have type α tree α tree. What happens to a leaf? What must happen to a branch and its sub-trees?

3

If the two trees are both Lf, they have the same shape. What if they are both branches? What if one is a branch and the other a leaf or vice versa?

4

We have already written a function for inserting an element into an existing tree.

5

Try using list dictionaries as an intermediate representation. We already know how to build a tree from a list.

6

Consider using a list of sub-trees for a branch. How can we represent a branch which has no sub-trees?

Chapter 12. In and Out

1

You can use the print_string and print_int functions. Be careful about what happens when you print the last number.

2

You can use the read_int function to read an integer from the user. Be sure to give the user proper instructions, and to deal with the case where read_int raises an exception (which it will if the user does not type an integer).

3

One way would be to ask the user how many dictionary entries they intend to type in first. Then we do not need a special code to signal the end of input.

4

Try writing a function to build a list of integers from 1 to n. Can you use that to build the table and print it? The iter and/or map functions may come in useful. Deal with a channel in your innermost function – the opening and closing of the file can be dealt with elsewhere.

5

The input_line function can be used – how many times can you call it until End_of_file is raised?

6

We can read lines from the file using input_line and write using output_string – make sure the newlines do not get lost! How do we know when we are done? Write a function to copy a line from one channel to another – we can deal with opening and closing the files separately.

Chapter 13. Putting Things in Boxes

1

Consider the initial values of the references, and then work through how each one is altered by each part of the expression. What is finally returned as the result of the expression?

2

Try creating a value for each list in OCaml. Now try getting the head of the list, which is a reference, and updating its contents to another integer. What has happened in each case?

3

Try writing a function forloop which takes a function to be applied to each number, and the start and end numbers. It should call the given function on each number. What should happen when the start number is larger than the end number?

4

Type them in if you are stuck. Can you work out why each expression has the type OCaml prints?

5

We want a function of type int array int. Try a for loop with a reference to accumulate the sum.

6

Consider swapping elements from opposite ends of the array – the problem is symmetric.

7

To build an array of arrays, you will need a use Array.make to build an array of empty arrays. You can then set each of the elements of the main array to a suitably sized array, again created with Array.make. Once the structure is in place, putting the numbers in should be simple.

8

What is the difference between the codes for ’a’ and ’A’? What about ’z’ and ’Z’?

Chapter 14. The Other Numbers

1

Consider the built-in functions ceil and floor.

2

This is simple arithmetic. The function will take two points and return another, so it will have type float × float float × float float × float.

3

Consider the built-in function floor. What should happen in the case of a negative number?

4

Calculate the column number for the asterisk carefully. How can it be printed in the correct column?

5

You will need to call the star function with an appropriate argument at points between the beginning and end of the range, as determined by the step.

Chapter 15. The OCaml Standard Library

1

You can assume List.rev which is tail-recursive.

2

You might use List.map here, together with List.mem

3

The String.iter function should help here.

4

Try String.map supplying a suitable function.

5

Consider String.concat.

6

Create a buffer, add all the strings to it in order, and then return its contents.

7

String.sub is useful here. You can compare strings with one another for equality, as with any other type.

Chapter 16. Building Bigger Programs

1

You will need to alter the Textstat module to calculate the histogram and allow it to be accessed through the module’s interface. Then, alter the main program to retrieve and print the extra information.

2

You will need functions to read and write the lines. You can read the required input and output filenames from Sys.argv. What should we do in case of an error, e.g. a bad filename?

3

Consider doing something a very large number of times. You should avoid printing information to the screen, because the printing speed might dominate, and the differing computation speeds may be hard to notice.

4

Start with a function to search for a given string inside another. You might find some functions from the String module in the OCaml Standard Library to be useful, or you can write it from first principles. Once this is done, the rest is simple.

Coping with Errors

It is very hard to write even small programs correctly the first time. An unfortunate but inevitable part of programming is the location and fixing of mistakes. OCaml has a range of messages to help you with this process.

Here are descriptions of the common messages OCaml prints when a program cannot be accepted or when running it causes a problem (a so-called “run-time error”). We also describe warnings OCaml prints to alert the programmer to a program which, though it can be accepted for evaluation, might contain mistakes.

Errors

These are messages printed when an expression could not be accepted for evaluation, due to being malformed in some way. No evaluation is attempted. You must fix the expression and try again.

Syntax error

This error occurs when OCaml finds that the program text contains things which are not valid words (such as if, let etc.) or other basic parts of the language, or when they exist in invalid combinations – this is known as syntax. Check carefully and try again.

OCaml

#1 +;;
Error: syntax error

OCaml has underlined where it thinks the error is. Since this error occurs for a wide range of different mistakes and problems, the underlining may not pinpoint the exact position of your mistake.

Unbound value …

This error occurs when you have mentioned a name which has not been defined (technically “bound to a value”). This might happen if you have mistyped the name.

OCaml

# x + 1;;
Error: Unbound value x

In our example x is not defined, so it has been underlined.

This expression has type … but an expression was expected of type …

You will see this error very frequently. It occurs when the expression’s syntax is correct (i.e. it is made up of valid words and constructs), and OCaml has moved on to type-checking the expression prior to evaluation. If there is a problem with type-checking, OCaml shows you where a mismatch between the expected and actual type occurred.

OCaml

# 1 + true;;
Error: This expression has type bool but an expression was expected of type
int

In this example, OCaml is looking for an integer on the right hand side of the + operator, and finds something of type bool instead.

It is not always as easy to spot the real source of the problem, especially if the function is recursive. Nevertheless, a careful look at the program will often shine light on the problem – look at each function and its arguments, and try to find your mistake.

This function is applied to too many arguments

Exactly what it says. The function name is underlined.

OCaml

# let f x = x + 1;;
val f : int -> int = <fun>
# f x y;;
Error: This function is applied to too many arguments;
maybe you forgot a `;'

The phrase “maybe you forgot a ‘;’ ” applies to imperative programs where accidently missing out a ‘;’ between successive function applications might commonly lead to this error.

Unbound constructor …

This occurs when a constructor name is used which is not defined.

OCaml

# type t = Roof | Wall | Floor;;
type t = Roof | Wall | Floor
# Window;;
Error: Unbound constructor Window

OCaml knows it is a constructor name because it has an initial capital letter.

The constructor … expects … argument(s), but is applied here to … argument(s)

This error occurs when the wrong kind of data is given to a constructor for a type. It is just another type error, but we get a specialised message.

OCaml

# type p = A of int | B of bool;;
type p = A of int | B of bool
# A;;
Error: The constructor A expects 1 argument(s),
but is applied here to 0 argument(s)

Run-time Errors

In any programming language powerful enough to be of use, some errors cannot be detected before attempting evaluation of an expression (until “run-time”). The exception mechanism is for handling and recovering from these kinds of problems.

Stack overflow during evaluation (looping recursion?)

This occurs if the function builds up a working expression which is too big. This might occur if the function is never going to stop because of a programming error, or if the argument is just too big.

OCaml

# let rec f x = 1 + f (x + 1);;
val f : int -> int = <fun>
# f 0;;
Stack overflow during evaluation (looping recursion?).

Find the cause of the unbounded recursion, and try again. If it is really not a mistake, rewrite the function to use an accumulating argument (and so, to be tail recursive).

Exception: Match_failure …

This occurs when a pattern match cannot find anything to match against. You would have been warned about this possibility when the program was originally entered. For example, if the following function f were defined as

let f x = match x with 0 -> 1

then using the function with 1 as an argument would produce:

OCaml

# f 1;;
Exception: Match_failure ("//toplevel//", 1, 10).

In this example, the match failure occurred in the top level (i.e. the interactive OCaml we are using), at line one, character ten.

Exception: …

This is printed if an un-handled exception reaches OCaml.

OCaml

# exception Exp of string;;
exception Exp of string
# raise (Exp "Failed");;
Exception: Exp "Failed".

This can occur for built-in exceptions like Division_by_Zero or Not_found or ones the user has defined like Exp above.

Warnings

Warnings do not stop an expression being accepted or evaluated. They are printed after an expression is accepted but before the expression is evaluated. Warnings are for occasions where OCaml is concerned you may have made a mistake, even though the expression is not actually malformed. You should check each new warning in a program carefully.

This pattern-matching is not exhaustive

This warning is printed when OCaml has determined that you have missed out one or more cases in a pattern match. This could result in a Match_failure exception being raised at run-time.

OCaml

# let f x = match x with 0 -> 1;;
Warning 8 [partial-match]: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
1
val f : int -> int = <fun>

Helpfully, it is able to generate an example of something the pattern match does not cover, so this should give you a hint about what has been missed out. You may ignore the warning if you are sure that, for other reasons, this case can never occur.

This match case is unused

This occurs when two parts of the pattern match cover the same case. In this situation, the second one could never be reached, so it is almost certain the programmer has made a mistake.

OCaml

# let f x = match x with _ -> 1 | 0 -> 0;;
Warning 11: this match case is unused.
val f : int -> int = <fun>

In this case, the first case matches everything, so the second cannot ever match.

This expression should have type unit

Sometimes when writing imperative programs, we ignore the result of some side-effect-producing function. However, this can indicate a mistake.

OCaml

# f 1; 2;;
Warning 10: this expression should have type unit.
- : int = 2

It is better to use the built-in ignore function in these cases, to avoid this warning:

OCaml

# ignore (f 1); 2;;
- : int = 2

The ignore function has type α unit. It has no side-effect.