A Tour Through OCaml with Project Euler

This post covers some of the major features of OCaml while solving Problem 1 from Project Euler.

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.

Let’s get started on this problem by looking at the math facilities provided by OCaml. Start a new interactive session using utop.

1
$ utop

Now we can play with some math. Note that to end a statement in OCaml you use not one, but two, semi-colons (;;). This is a peculiarity of an OCaml interactive environment and not necessary for standalone compiled programs.

Integers and Floats

1
2
utop # 2 + 3;;
- : int = 5

Our problem asks us to find multiples of natural numbers, which are divide with a remainder of zero.

1
2
3
4
5
6
7
8
utop # 9 / 3;;
- : int = 3
utop # 5 / 5;;
- : int = 1
utop # 10 / 3;;
- : int = 3
utop # 10.0 / 3.0;;
- : int = 3

Trying this a few times, we can see that the division operator / on values of type int (integers) returns a value of type int and ignores the remainder. In fact, the / operator is only valid for integer types. Try it on values of type float:

1
2
3
4
utop # 10.0 / 3.0 ;;
Line 1, characters 0-4:
Error: This expression has type float but an expression was expected of type
         int

OCaml uses notation to distinguish between float and int. Numeric literals followed by a . are of type float, and infix operators followed by a . are of type float. For example, the expression 2.5 +. 6. is valid, whereas the expression 2.5 +. 6 fails type checking because the literal 6 is of type int not float (i.e. we forgot the . after 6). Thankfully, the compiler is at least kind enough to give us a hint:

1
2
3
4
5
6
7
utop # 2.5 +. 6. ;;
- : float = 8.5
utop # 2.5 +. 6 ;;
Line 1, characters 7-8:
Error: This expression has type int but an expression was expected of type
         float
  Hint: Did you mean `6.'?

In general, OCaml requires you to be explicit about the numeric types you use.

Let Bindings and Variables

To solve our problem, we will likely need some variables for remembering the value of certain expressions. In OCaml, the let keyword creates a binding between a value and a name. For example, to give the value of the expression 4 + 3 the name x we use a let binding that we can refer to later:

1
2
3
4
5
6
# let x = 4 + 3;;
val x : int = 7
# let y = 5 + 2;;
val y : int = 7
# x + y;;
- : int = 14

The only gotcha with OCaml syntax is that variables must start with a lowercase letter — uppercase notation is reserved for constructors.

1
2
3
# let X = 4 + 3;;
Line 1, characters 4-5:
Error: Unbound constructor X

Functions are also defined using let bindings. In OCaml, functions are first-class and are treated like any other value — there is no difference between using a let binding to name a value and using one to name a function. In OCaml, function arguments are separated by spaces instead of by a combination of parentheses and commas. The following let binding creates the variable named square and refers that variable to a function taking an integer variable x and computing x * x:

1
2
# let square x = x * x;;
val square : int -> int = <fun>

The OCaml compiler was able to intuit the type of our function by the type of the parameters and return value. Here, int -> int is a function type indicating that the parameter to the function is of type int and that the return value from the function is also of type int.

Lists

We will need to iterate over the numbers up to 1000 to solve our problem, which makes the List module useful. Within the List module, we can access the range function to create a sequence of numbers between a range:

1
2
3
4
# List.range 1 1000;;
- : int list =
[1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20; 21; 22;
 23; 24; 25; 26; 27; 28; 29; ...]

Filtering is a common operation in functional programming that condenses a list to include only values that meet certain criteria. To do this, we require a function that returns a boolean stating whether or not our criteria for list inclusion is met. For our problem, that criteria is whether or not a number is cleanly divisible by three or five. Here, I introduce the % operator which returns the remainder of division, the boolean equality infix operator =, and the logical OR operator ||. The type signature of our function shows that it takes a parameter of type int and returns value of type bool.

1
2
# let is_multiple x = (x % 3 = 0 || x % 5 = 0);;
val is_multiple : int -> bool = <fun>

We can use is_multiple to test if values are divisible by three or five:

1
2
3
4
# is_multiple 10;;
- : bool = true
# is_multiple 8;;
- : bool = false

Now let’s take a closer look at how to filter our list using the filter function. Calling the function with no arguments from utop gives us a hint at how to use the function by examining it’s type signature.

1
2
# List.filter;;
- : 'a list -> f:('a -> bool) -> 'a list = <fun>

Type signatures take some practice to read. This one states that filter is a function of two arguments: 'a list which is a list of unknown or generic type ('a), and a function f that takes a parameter of the same type of the list ('a) and returns a bool (this is our is_multiple function).

1
2
3
'a list  * a list of type 'a
f:('a -> bool)  * a function with parameter type 'a returning type bool
'a list  * a list of type 'a

This type signature also introduces the concept of named or labeled arguments. You will notice the notation f: in the function signature. This notation assigns a label to the parameter and allows us to pass a labeled argument that gets passed to the correct label in the function signature using ~ syntax. Labeled arguments have no concept of position and can be used anywhere when calling the function. For example, the following are equivalent:

1
2
3
4
List.filter ~f:is_multiple [10; 9; 8];;
- : int list = [10; 9]
List.filter [10; 9; 8] ~f:is_multiple;;
- : int list = [10; 9]

Note that in OCaml, list members are separated by ;, not ,!

Solving Problem 1

We now have enough OCaml under our belts to solve Problem 1 of Project Euler. We can start by combining List.range, List.filter, and is_multiple to create a list of integers that are multiples of 3 and 5:

1
2
# let is_multiple x = (x % 3 = 0 || x % 5 = 0);;
# List.filter (List.range 1 1000) ~f:is_multiple;;

What’s left is to sum the result, which can be done using the List.fold function from the Base module. In functional programming, the concept of folding over a list applies a function to each element of the list recursively, starting with a base condition. For addition, we take each element of the list, apply the addition function, and then call fold on the rest of the list. For example, if we have the list [1; 2; 3] and an initial condition 0, folding the list using addition can be expanded in a simplified way as:

1
2
3
4
List.fold ~f:(+) ~init:0 [1; 2; 3];;
List.fold ~f:(+) ~init:(0 + 1) [2; 3];;
List.fold ~f:(+) ~init:(0 + 1 + 2) [3];;
List.fold ~f:(+) ~init:(0 + 1 + 2 + 3) [];;

Since there are no more elements in the list, the folding operation is complete and we can return the summation. We can now add folding to our solution to finish Project Euler’s problem 1.

1
2
3
# let is_multiple x = (x % 3 = 0 || x % 5 = 0);;
# List.fold ~f:(+) ~init:0 (List.filter (List.range 1 1000) ~f:is_multiple);;
- : int = 233168
fp  ocaml  euler 

See also

comments powered by Disqus