OCaml for the Skeptical
U of C Library DLDC Informal OCaml Class

Control Structures

In a functional language, not many control structures are needed; functions can do almost everything. The only control structure that's really necessary is a conditional expression (and even that's not necessary in a lazy language[1]).

But OCaml, as an impure functional language, also provides control structures for iteration, and pattern matching combines a multi-armed conditional, unification, data destructuring and variable binding into one powerful control structure.

Conditional Expression

OCaml's conditional expression is if expr then expr else expr. There's nothing special to say about it, as long as you remember that you use parentheses around any expr only if necessary to disambiguate, and that the entire conditional is itself an expression (which of course may sometimes require parens around the entire conditional). Layout is completely up to you; here are three of the possibilities:

if b <> 0 then a / b else 1
if b <> 0 then
  a / b
else
  1
if b <> 0
then a / b
else 1

While Loop and For Loop

Most OCaml programmers do a lot of their loops via recursive functions. However, there are two imperative loops: a conventional while loop, and a counting for loop like that of Algol 60.

For Loop

The for loop is the easiest to use, since it's more structured and thus can hide some of its imperativeness. It's a typical counting loop that takes one of the following two forms:

    for name = expr1 to expr2 do expr3 done
    for name = expr1 downto expr2 do expr3 done

The name is bound, in the loop body expr3, to the integer values between expr1 and expr2 inclusive, either increasing (to) or decreasing (downto). So expr3 is executed repeatedly, which will only be useful if it contains a side-effect (e.g. I/O or the modification of a mutable data structure like a ref, record, array or string). The entire for loop is an expression, but it's value is always () (the unit value).

Here's a trivial example that uses I/O as the side-effect:

    # for i = 1 to 4 do print_endline (string_of_int i) done;;
    1
    2
    3
    4
    - : unit = ()
    #

string_of_int converts an int to a string, and print_endline prints a string to standard output, terminating it with a newline.

Here's an example modifying a ref in the loop body to implement an iterative factorial:

    # let fac n =
	let result = ref 1 in
	  for i = 2 to n do
	    result := !result * i
	  done;
	!result
      ;;
    val fac : int -> int = <fun>
    # fac 6;;
    - : int = 720
    #

Note the let that establishes a ref as an accumulator. It's important that this let be wrapped around the for so that each iteration modifies the same reference! Similarly, you can't use a lambda variable (i.e. function parameter or let binding) to accomplish this:

    # let fac n = (* bogus! *)
	let result = 1 in
	  for i = 2 to n do
	    let result = result * i in ()
	  done;
	  result
	;;
    val fac : int -> int = <fun>
    # fac 6;;
    - : int = 1
    #

This fails because, as we've seen before, the outer and inner bindings of result are completely unrelated variables, and also because the inner result is only bound in the inner let body (which is () for reasons of type consistency). As each iteration finishes, the inner let body is exited and the local binding of result is thrown away. The result of the function will be 1 for any value of n.

While Loop

The while loop is much like that of C or most any imperative language. The syntax is:

    while expr1 do expr2 done

Here's an implementation of the Unix cat command, i.e. a function that takes a filename and copies the contents of that file to standard output; the input function, a (mutable) string buffer, and a loop-controlling reference (eof) provide the needed side-effects:

    # let cat filename =
      let chan = open_in filename in
      let size = 4 * 1024 in
      let buffer = String.create size in
      let eof = ref false in
	while not !eof do
	  let len = input chan buffer 0 size in
	    if len > 0
	    then print_string (String.sub buffer 0 len)
	    else eof := true
	done;;
      val cat : string -> unit = <fun>
    # cat "/etc/motd";;
    FreeBSD 4.6.2-RELEASE-p14 (JFCL) #2: Fri Oct  3 17:01:27 CDT 2003

    jfcl: The University of Chicago Library: Thu Apr  1 15:54:06 CST 2004

    This is jinn via ssh.
    - : unit = ()
    #

This function is written almost exactly the way you'd do it in C. There are several points to note:

Footnotes

  1. A conditional isn't theoretically necessary even in an eager language, but the technique that replaces it is too tedious for ordinary use.