Computer Science Canada

[O'Caml-tut] Imperative Syntax

Author:  wtd [ Fri Jul 23, 2004 9:20 pm ]
Post subject:  [O'Caml-tut] Imperative Syntax

Along with its support for functiuonal programming techniques, O'Caml also offers syntax more familiar to those who have coded in imperative programming languages.

If ... Else

code:
if 1 = 0 then "They match!" else "They don't match, of course."


This is considered by O'Caml to be a single expression, though in it we see three expressions.

  1. code:
    1 = 0

    This expression tests to see if 1 equals 0.
  2. code:
    "They match!"

    Just a literal value... a string.
  3. code:
    "They don't match, of course."

    Again, a literal value.


Any single expression can follow "then". Thus, to get "else if" is as simple as:

code:
if 1 = 0 then "foo" else if 1 = 2 then "bar" else "baz"


If any of the string literals used in the above code needs to be multiple expressions, then "begin" and "end" can be used to group multiple expressions into a single expression.

As an example, the below checs to see if a user input the correct password. It's extremely simple. Prompt and read a password once. If the input if correct, output that input. If it's not, prompt again and return the new input.

code:
let password = "God" and
user_input = print_string "input a password: "; read_line () in
   if password = user_input then user_input
   else begin
      print_endline "bad_input... try again";
      read_line ()
   end


Loops

O'Caml contains both a while loop and a for loop. Both are quite versatile. They evaluate a sequence of expressions until a condition becomes false. The only real requirement is that the seqence of expressions evaluates to "unit" (O'Caml's equivalent to "void").

I'll start out with something as simple as a count from 0 to 10.

code:
for i = 0 to 10 do print_int i done


Or we could go from 10 to 0.

code:
for i = 10 downto 0 do print_int i done


And a while loop can do either, when combined with a reference. A reference is essential in this case because with normal O'Caml behavior, a name bound to a value can only be rebound to a new value, and rebinding within the loop wouldn't have any effect the next time around.

The ! and := are the only strange looking bits of code in this case. ! dereferences a reference, and := assigns to a reference.

code:
let i = ref 0 in
   while !i <= 10 do
      print_int !i;
      i := !i - 1
   done


Using functional patterns, we could simply write out a list of ints with the following.

code:
List.iter print_int [1;2;5;4]


But perhaps we want to prove that it can be done in a procedural/imperative way.

code:
let list = [1;2;5;4] in
   for i = 0 to (List.length list) - 1 do
      print_int (List.nth list i)
   done


Exceptions

Exceptions are delightfully simple in O'Caml.

I used exception handling in creating a recursive function which would read all lines of input from a file. The exception my function depends on is End_of_file, which is raised whenever you try to read beyond the end of a file.

I pass a file into a function. The program then tries to read a line from that file. If it's successful, it adds to that line the result of calling the function again with the same file. If it raises the End_of_file exception, as it will once the file has been completely read, it returns an empty list. This ends the recursion.

code:
let rec input_lines file =
   try let line = input_line file in
      line :: input_lines file
   with End_of_file -> []


Of course, it's equally easy to create our own exceptions.

code:
exception Somethings_wrong


This is about as simple an exception as anyone could hope for. Raising it is as smple as:

code:
raise Somethings_wrong


And catching it is as simple as:

code:
try raise Somethings_wrong with Somethings_wrong -> "Error occurred"


Of course, this doesn't provide any information about what went wrong. To have an argument for an exception that can be set when the exception is raised, we simply change the above to:

code:
exception Somethings_wrong of string

try raise (Somethings_wrong "hello world!") with Somethings_wrong err_msg -> "Error occurred: " ^ err_msg


Now, what if we want to handle different error messages differently?

code:
try raise (Somethings_wrong "hello world!") with
   Somethings_wrong "Foo!" -> "Error occurred, but ignore it..."
   | Somethings_wrong err_msg -> "Error occurred, somebody's playing games with us..."
   | Somethings_wrong err_msg -> "Error occurred: " ^ err_msg


: