Programming Assignment 0

(not turned in or graded)

The purpose of this document is to give you a few pointers about using OCaml as you begin to dive into the material from lectures and in subsequent programming assignments.

OCaml in the browser

OCamlPro offers an OCaml REPL that you can try in the browser. You may want to play around with this until the course accounts get set up properly, or any time if you need to try something quickly.

OCaml interpreter (ocaml)

The OCaml interpreter ocaml is a read-eval-print-loop (REPL) loop that is particularly helpful when learning the language and experimenting with features. We use % to denote the Unix shell prompt (this may be different for you) and # for the OCaml prompt. When at the REPL, enter ;; to denote the end of a top-level expression for the interpreter to evaluate.

% ocaml
        Objective Caml version 3.12.0

# 3 + 5 ;;
- : int = 8
# let hello () = "Hello!" ;;
val hello : unit -> string = <fun>
# hello () ;;
- : string = "Hello!"

Hit Ctrl-D or type exit 0;; at the prompt to exist the OCaml shell.

# exit 0;;
%

OCaml Source Files

To avoid typing the same expressions into the REPL repeatedly, you can save code in an .ml source file and then load it into the REPL with the #use directive (the file must be present in the same directory as the one from which you launched the OCaml shell):

# #use "foo.ml";; (* file defining hello *)
- : unit = ()
val hello : unit -> string = <fun>
# hello ();;
- : string = "Hello!"

This has the same effect as typing out the file at the prompt. However, it is far more convenient to type and edit the file with Vim/Emacs/GEdit and load it with #use. Don’t forget to save the file in the editor before you (re)load it in the shell! If you are familiar with Emacs, you can use the Emacs Mode.

A source file can also be run through the interpreter from the command-line:

% ocaml foo.ml
In .ml source files, you may denote the end of “top-level expressions” with ;;, but these are optional.

Saving REPL History with rlwrap

The ordinary interpreter does not allow you to “scroll through” previous expressions that you have entered. Fortunately, there is a terrific utility called rlwrap that can help. Running

% rlwrap ocaml
will run the ocaml interpreter such that you can use the up and down keys at the REPL to recall your previous interactions.

You will likely want to run ocaml like this every time, so you can modify your shell settings to avoid typing this every time you launch the REPL. For example, if you are running the bash shell, you can add

alias ocaml="rlwrap ocaml"
to your ~/.bashrc or ~/.bash_profile so that you can simply type % ocaml from the shell. (Note: this will take effect in all newly-opened terminals. To have an effect on the current session, you would have to run something like % source ~/.bashrc).

OCaml compiler (ocamlc or ocamlopt)

OCaml also comes with mature byte-code and native-code and compilers. Feel free to learn more about these, but for most assignments we will stick to the interpreter for simplicity.

OCaml core library

We say that the built-in, or native, constructs of OCaml are those that are defined in the Pervasives module. All of the values defined in Pervasives are automatically in scope for any OCaml program.

Notice that even operators, such as +, &&, ^, etc. are functions that can be applied just like any other function. Using parentheses around an operator will treat it just like any other expression, whereas omitting the parentheses requires the operator to be used in infix notation.

# (||);;
- : bool -> bool -> bool = <fun>
# let identityBool = (||) false;;
val identityBool : bool -> bool = <fun>
# identityBool true;;
- : bool = true 
# identityBool false;;
- : bool = false

The core library provides functions for printing to standard output and reading from standard input. For example:

# let _ = print_string "What’s your name? " in
  let s = read_line () in
    print_string ("Hello, " ^ s ^ "!\n");;
What’s your name? Ravi
Hello, Ravi!
- : unit = ()

OCaml standard library

The so-called standard library of OCaml has many modules that provide useful functions, including Printf and String .

# Printf.printf "Hello, %s!\n" "Ravi";;
Hello, Ravi!
- : unit = ()

In some of the assignments, you may be asked to write functions that implement similar functionality to functions provided by the standard library. The assignments will make clear which library functions, if any, you are allowed to use in your solutions.

You are encouraged to become familiar with the standard library, however, because they are essential resources once you become familiar with OCaml and start to build larger projects.

syntactic sugar for functions

Functions in OCaml are first-class values that can be bound to variables, supplied as arguments, etc, and all functions take a single argument. Because function definitions and “multi-argument” (so-called curried) function definitions are so common, OCaml provides several forms of syntactic sugar to make common patterns more readable.

For example, each of the following sets of functions is equivalent:

let foo x = x + 1
let foo = fun x -> x + 1

let bar x y = x + y
let bar x = fun y -> x + y
let bar = fun x -> fun y -> x + y

let baz (x,y) = x + y
let baz = fun (x,y) -> x + y

The OCaml compiler does a terrific job in automatically inferring types, so that no annotations need to be manually written. It is often useful, however, to write down types (at least for top-level functions) for documentation purposes, or for checking that a type holds when trying to debug type error messages.

OCaml allows optional type annotations for expressions. For example, the above functions can be annotated in several different ways:

let foo (x:int) : int = x + 1
let foo : int -> int = fun x -> x + 1

let bar (x:int) (y:int) : int = x + y
let bar : int -> int -> int = fun x y -> x + y

let baz ((x,y):(int*int)) : int = x + y
let baz ((x:int),(y:int)) : int = x + y;;
let baz : (int*int) -> int = fun (x,y) -> x + y

syntactic sugar for pattern matching

Pattern matching is used all the time, so OCaml provides several syntactic sugar mechanisms that allow common situations to be written more succinctly.

For example, patterns for cases that have the same expression body can separated with | so that the expression body does not have to be duplicated for each case.

Furthermore, because many functions immediately pattern match their arguments, the syntactic sugar function CASES is shorthand for fun x -> match x with CASES.

In particular, all of the following are equivalent:

let isOneOrTwo i = match i with 1 -> true | 2 -> true | _ -> false

let isOneOrTwo i = match i with 1 | 2 -> true | _ -> false

let isOneOrTwo = function 1 | 2 -> true | _ -> false

let isOneOrTwo = function
  | 1 | 2 -> true
  | _     -> false

This same syntactic sugar applies to pattern matching against datatypes, rather than base types, in just the same way:

type stuff = A of int | B of bool

let lookAtStuff x =
  match x with
    | A i -> ()
    | B b -> ()

let lookAtStuff = function
  | A i -> ()
  | B b -> ()

imperative features (gasp!)

Finally, lest you find out the truth from some stranger, it is true that OCaml does have imperative programming constructs, such as mutable variables and loops. For example:
# for i=1 to 10 do  print_int i; print_string " " done; print_newline() ;;  
1 2 3 4 5 6 7 8 9 10 
- : unit = ()
However, you must strike this from your memory, and you are most certainly not allowed to use such features unless explicitly instructed to. Besides, for all of the programming we will be doing in OCaml, solutions that avoid these features are much more likely to be readable, elegant, and correct.