OCaml Tutorial by Example

,

To run ocaml code, you can run it interactively in the command line. To start command line, type “ocaml”. You can also run it from a file. Suppose “mytest.ml” is your ocaml source file. Run it like this: ocaml mytest.ml.

Comments and Printing

(* comment. *)
(* comment can be (* nested *).*)

print_int 3;;
print_string "\n";;
print_float 3.0;;
print_string "\n";;
print_string "good!";;

Strings and Numbers

Example of strings:

print_string "Once upon a time …\n";;

(* Unicode ok, linebreak ok, “\n” ok *)
print_string "α β λ
≤ ≥ ≠ ⊂ ℚ ℝ ℂ ∑ ↔ ⇔ ◀▶▲▼\n";;

(* joining strings. Use “^” *)
print_string ("Once upon a time … " ^ "There was …");;

There are 2 built-in type for numbers: “int” and “float”. For example, -2, 3 are type int. Float are basically numbers with decimals. For example: 3., 0. are type float.

Ocaml does not mix types or automatically convert types. Even simple calculation such as 1 + 1 must have the right types. For example, 1 + 1. is a compiler error. You must use the right operator for data of the same type.

(* example of printing different types of values *)
print_int (3 + 4);; (* integer *)
print_string "\n";;
print_float (2. +. 7.);; (* float. *)
(* The “+.” is for adding number of float type *)

Arithmetics

Parenthesis is never used for function call such as f(3). Note: f(3) will work however because you are grouping 3 itself, then the function f will take its value as input.

(* examples of simple arithmetic with int type. *)

print_int (3 + 4);;
(* paren is needed, because if not, print_int will take 3 as its only
argument, which returns a null value of “unit” type, and unit + 4 will
result error because type mismatch. *)

print_string "\n";;

print_int ((3 + 4 - 5) / 2 );;
print_string "\n";;

print_int (3 * 4 - 5);;
print_string "\n";;
(* examples of computing with float type *)

(* you need to use -. +. *. /. etc for float type. *)
print_float (3. *. 4. -. 5.);;
print_string "\n";;

(* exponential operator is “**”, and it acts on float types. *)
print_float (3. ** 2.);;        (* prints 9 *)

(* There's no “**.” *)

Types

Ocaml has these built-in simple types: int, float, bool, char, string, unit.

(* example of values having built-in types *)

3 ;;    (* int *)
3. ;;   (* float *)
'x' ;;  (* char *)
"war and peace" ;; (* string *)
true;;             (* bool *)
false;;            (* bool *)
() ;;              (* unit *)

The type “unit” is similar to a “void”, “undefined”, “nil” in other languages. It is represented by the syntax (). For example, the return value of the print_string function is “unit”.

To convert a value's type, use one of the type converting functions:

(int_of_float 3.) + 1 ;; (* change float to int, then add 1 *)
(float_of_int 3) +. 2.2;; (* change int to float, then add a float. *)
print_string (string_of_float 3.2);; (* change float to string *)
print_string "\n";;
print_string (string_of_int 3);; (* change int to string *)

True and False

“true” and “false” are literals of type “bool” (“bool” means boolean). In most other lang, you have to think about whether 0 or -1 evaluate to true or false, or other special things like nil, null, undefined function, empty string, empty list, etc. Because Ocaml is strict on type, you don't have these issues, because you can't use anything that's not a boolean type in a place that expects boolean. If you do, it is a compiler error.

Here are some example of boolean expressions:

Printf.printf "%B" (3 < 4);;            (* prints true *)

Printf.printf "%B" (true && false);;    (* prints false *)

Printf.printf "%B" (true || (3 < 4));;    (* prints true *)

If Then Else

let x = 5;;
if x == 3 then print_string "yay\n" else print_string "nay\n";;
if x >= 3 then print_string "yay\n";;

The expressions for “then” and “else” MUST match type.

If you do not use “else”, then the value of the “then” section must be the “unit” type.

(* example of nested “if then” *)
let x = 5;;
let y = 3;;

print_string
(if x == 5 then
   (if y == 3 then "y is 3" else "y is not 3")
 else "x ≠ n");;
(* indentation doesn't matter *)

“If then else” is a expression. It returns a value.

let x = if true then 4 else 9;;         (* x is now 4 *)

Variables

Variables MUST start with lower case. Example of global variables:

let b = 4;;
let b = 5;;
print_int b;; (* now b is 5 *)

Local variables are defined using the form let ‹varname› = ‹value› in ‹body›. The “‹body›” is any expression. In the body, any “varname” will be replaced by its value.

let b = 4 in 2 + b;; (* b is 4 here *)

print_int b;; (* b is undefined here *)

Basically, the in … acts like a bracket “{…}” in other langs. Text after the “in” is a block of code. The use of “in” can be nested. Example:

(* nesting of local variable blocks *)
let b = 4 in
let b = b + 1 in
  b;;  (* ⇒ 5 *)

(* can also be written like this: *)
let b = 4 in let b = b + 1 in b;;       (* ⇒ 5 *)
(* tab and return chars in source code have no special meaning *)

Indentation does not have special meaning in Ocaml.

Mutable Variables

The let x = 4 in … construct is actually like defining a constant. The variable is set to a value and is not allowed to be reset to something else. To have a mutable variable, use the “ref” keyword in the value. (the “ref” means reference). Example:

(* declare x to be a mutable variable of type “int ref” *)
let x = ref 3;;

(* reset the value of x *)
x := 4;;

(* syntax for getting the value of x *)
!x;;

x := !x + 1;;  (* increase x by 1 *)

print_int !x;;

List Kind Of Things

Lists

The syntax for list is [ ‹element 1› ; ‹element 2› ; ‹element 3› ; … ]. The last “;” is optional. All elements must be of the same type.

(* list examples *)

(* elements must all be the same type *)
let x = [ 2; 8; 5; ];;
List.length x;;                         (* ⇒ 3 *)

(* first element, called “head”. *)
List.hd x;;                             (* ⇒ 2 *)

(* rest elements, from 2nd to last, called “tail”. *)
List.tl x;;                             (* ⇒ [8; 5] *)

(* prepending a element *)
4::x;;                                  (* ⇒ [4; 2; 8; 5; ] *)

(* prepending several elements *)
1::9::4::x;;                            (* ⇒ [1; 9; 4; 2; 8; 5] *)

The List.hd is a form of calling the “hd” function from the module “List”.

A list allows you to efficiently grow the list, by adding to or removing the first element of the list. However, accessing nth element may be slow, if n is large.

Array

Array is like a list of fixed length, and every element must be the same type. The syntax for array is [| ‹element 1› ; ‹element 2› ; ‹element 3› ; … |].

(* array examples *)

(* array syntax. Elements must be same type *)
let x = [| 2; 8; 3 |];;

(* getting a element *)
print_int x.(1);;         (* prints 8. Index starts at 0 *)

(* changing array element *)
x.(1) <- 9;;             (* set 1st index element to 9 *)

(* creating a array programmatically *)
(* first arg is num of items, second is init value *)
let x = Array.make 9 4;;

The difference between list and array is that you cannot add or remove items into a array. However, getting and setting arbitrary elements in array is fast, while getting/setting arbitrary elements in list is slow.

n-tuples

A n-tuple is like a array but the elements needs not to have the same type.

(* arbitrary n-tuple can be created *)
let x = (3,4,8);;                 (* 3-tuple *)
let y = (7.8, "toh", 4, 8);;      (* 4-tuple *)

If you have 2-tuple, then you can use the buildin function “fst” and “snd” to access the 2-tuple's elements. However, they don't work for n-tuple in general. You need to define your own.

Records

A Record datatype is similar to keyed list, hash table, dictionary, associative list, in other languages. Basically, it's like a list, and each element in the list is a Key/Value pair.

(* defines a keyed list type, called “record” in Ocaml *)

(* define a type “person” that is a particular subset of
   a compound type called Record *)
type person = {mutable name:string; mutable age:int };;

let mj = {name="Mary Jane"; age=19 };;

(* extracting a field *)
print_string mj.name;                   (* prints “Mary Jane” *)

(* changing a field's value *)
mj.name <- "Mary Johnson";

print_string mj.name;                   (* prints “Mary Johnson” *)

Nesting List-Like Things

List, array, tuple can be nested arbitrarily, but remember, for list and array, their elements must all have the same type.

(* nesting. Remeber, all elements must be same type. *)
let x = [ [1;2]; [3;4;5]; [6;7] ];;       (* list of lists *)
let x = [ [|1;2|]; [|3;4;5|]; [|6;7|] ];; (* list of arrays *)
let x = [ (1,2); (3,4); (5,6) ];;         (* list of tuples *)
(* nested array examples *)
let x = [| [|3; 4|]; [| 5; 6|] |] ;;    (* array of arrays *)
let x = [| [3; 4]; [ 5; 6] |] ;;        (* array of lists *)
let x = [| (3, "4"); (5, "7") |] ;;     (* array of tuples *)

Note: when you have tuples as elements to list or array, each tuple must be the same type. That means, you cannot have one tuples (3,4) and (3.1, 4) because the first is of type “int*int” and the second is “float*int”.

Note also the length of tuple must match. You cannot have elements (3,4) and (3,4,5) in a array or list. This is because, technically, (3,4) is of type “int*int” but (3,4,5) is of type “int*int*int”.

[| (3 , "4"); (3. , "4") |] ;;    (* compiler error. *)
[| (3 , "4"); (3  , "4", 5) |] ;; (* compiler error. *)
(* a tuple, with elements being int, list, tuple, array *)
let x = (3, [4;5;6], (7,8), [| 9; 10 |]);;

Tuple is the most flexible about types.

Function

Here's a very basic form of function definition. Notice how this is similar to defining variables, and how paren is NOT used for function call.

let f x = x + 1;;
f 3;;                                   (* ⇒ 4 *)

let g x = x +. 1.;;
g 3.;;                                  (* ⇒ 4. *)

(* Note how types and operators on them must match *)

(* function of 2 parameters *)
let f x y = x + y;;
f 3 4;;                                 (* ⇒ 7 *)

Function As Expression

Function in ocaml is a expression. That means, a function is a value that represents the function. This is also called anonymous function, lambda, pure function, function expression.

(* syntax for a function expression *)
fun n -> n + 1;;

(* applying a function to a value *)
(fun n -> n + 1) 2;;                    (* ⇒ 3 *)

(* function can be assigned to variable *)
let f = fun n -> n + 1;;
f 2;;                                   (* ⇒ 3 *)

(* syntax of function with 2 parameters *)
fun x y -> x +. y ;;

(* apply it to 2 arguments *)
(fun x y -> x +. y) 3. 4. ;;            (* ⇒ 7. *)

(* note how paren is used for grouping, not function call *)

Examples

In the following, you define a function myComposition that returns the function composition of 2 other functions. “myComposition” takes 2 arguments, f and g, each one is a function. It returns a function. When the result function is applied to a argument x, it is equivalent to f(g(x)).

(* example of defining your own function composition function *)

let myComposition f g = (fun x -> f (g x) );;

(* applying myComposition to 2 arguments, then apply the result to "a" *)
myComposition (fun x -> x ^ "c") (fun x -> x ^ "b") "a";;

In the above, the myComposition is given 2 arguments. Each one is a function, that takes a string input and output with another char appended. When myComposition is given these two input, and the output is applied to the argument "a", the result is "abc".

In the following example, we define a function named “apply”. “apply” takes 2 arguments, f and x. When called, it computes “f(x)”.

(* example of defining your own “apply” function *)
let apply = fun a -> ( fun b -> a b );;

(* define a function f *)
let f = fun x -> x+1;;

(* apply f to the value 3 *)
print_int (apply f 3);;                 (* prints 4 *)

(* define another function g *)
let g = fun x -> x+1;;

(* apply one of f or g, to the value 0 *)
print_int ( apply (if true then f else g) 0 );;  (* ⇒ 1 *)

In the above, we defined the variable “apply” using the “let apply =” form. On the right hand side, we have fun a -> (…), which means a function with one parameter, and this parameter is named “a”.

On the right hand side, is the body of the function fun b -> a b. This body is another function, with one argument “b”. The body of this function is “a b”. The syntax “symbol1 symbol2” means function symbol1 evaluated at symbol2. So, all of the above means, that “apply” is now a function expression with one parameter, and this function expression will return another function expression with one parameter, and returns “a evaluated at b”.

The whole result of this is that “apply” is a function with 2 parameters, say “a” and “b”, and return “a” evaluated at “b”.

Automatic Decomposition of Functions

Functions of more than 1 parameter can always be applied to just 1 argument. It returns a function expression, such that when this expression is used as a function by giving it more arguments, it returns the same result as original function with full arguments. (function decomposition in computer languages is also called “currying”.) Example:

let f n m = n + m;;
let g = f 3;;           (* g is now equivalent to: “fun x -> 3 + x” *)
g 4;;                   (* ⇒ 7 *)

(f 3) 4;;               (* don't need to define g first *)

Recursive Functions

Functions that are defined by referring to itself, must be declared with “rec”.

(* example of factorial function *)
let rec f n = if n == 1 then 1 else n * f (n-1);;
f 3                                     (* ⇒ 6 *)

(* example of fibonacci sequence. *)
(* nth term is sum of previous 2 terms. Starting with 0 and 1. *)
let rec f n = match n with 0 -> 0 | 1 -> 1 | x -> f (n-1) + f (n-2);;
f 9;;                                  (* ⇒ 34 *)

(* Note: above are for example only. Not efficient *)

For more detail about functions, see: OCaml Tutorial: Function.

Loading File and Module

If you have a source file “x.ml”, you can load it, like this:

#use "xx.ml";;                          (* loading a file *)

(* all functions in the file will be usable here *)
print_int (f 3);;
blog comments powered by Disqus