Ocaml Basics for Beginners

Ocaml Basics for Beginners

Ocaml is a statically typed functional language.

Instead of using manual type annotations, it handles type inference using the Hindley-Milner algorithm.

The interpreter binary is ocaml & compiler is ocamlopt. 

Setup

You can quickly try out small pieces of code or exercises by using the top-level, which is essentially a REPL (read -> evaluate -> print -> loop) for Ocaml.

You can follow this guide for setup and install.

Let Bindings

let is used to declare constants, variables, or functions.

You can chain let ... in construct like below:

ocaml

let x = 10 in
let y = 20 in
x+y
;

Let Ocaml bindings differ from declaring a variable in other imperative programming languages. Here, let is not mutable, i.e. the value of a let binding cannot be changed once it has been initialized. This is similar to the mathematical declaration of variables (y = f(x)), the value of y may be different for different values of x, but y cannot itself vary for the same value of x.

Functions

Below is a function to add two values, x & y. The final evaluated expression is returned as a result; there is no need to add any return keyword like Python, or Java.

Function Syntax

ocaml

(* let {function_name} *{variables} = {function_body} *)

let add x y = x + y

(* => Toplevel: int -> int -> int *)

If you declare this function in toplevel, it will print the function signature with the types of all input variables, followed by the function's return type.

In the above case, the type of x and y is int, and the last type represents the return type, which is also int.

The apostrophe as a suffix to var name " a' " represents the temporary declaration of variable a. This is similar to a_tmp in other languages.
The apostrophe as a prefix to a var name " 'a " means it's of arbitrary type.

Recursive function

ocaml

let rec fact n = if n=0 then 1 else n * fact (n-1);

(* Unit function *)
let print_hello = print_int "Hello, learning Ocaml!"

In order to make a function recursive, you need to add the rec keyword before the function name.

Parenthesis is not required while calling a function unless the argument is an expression.

A function that does not return any value is known as a unit function

Partial functions

ocaml

let add x y = x+y;
=> val add : int -> int -> int = <fun>

(* Partial function. inc_5 returns a function. *)
let inc_5 = add 5;
=> val add_5 : int -> int = <fun>

inc_5 3;
=> :int = 8

Above is the definition of how you can declare partial functions. Also called function currying.

Anonymous functions

ocaml

(* Declare anonymous functions *)

let my_lambda = fun x -> x * x;

Operators

There are built-in operators in Ocaml. However, there is little distinction between them. Operators can be called functions and overridden.

Ocaml refrains from type conversion like int to float; instead, there are separate operators for floats. Add . after operator to deal with float values as mentioned below.

ocaml

(* Operators for floats *)

let divide_5 y = y /. 5.;

=> val divide_5 : float -> float = <fun> 

let mean x y = (x +. y) /. 2.;

Data Structures

List

All values in a list should be of one type, separated by a semicolon. They are declared in square brackets. It's an immutable data structure.

ocaml

let lst = [8; 9; 8; 10];

let names = ["Mike"; "Sam"];

(* List Functions *)
List.nth names 1;
=> ""

List.map (fun x -> x* 2) last;
=> [16; 18; 16; 20]

(* Append element to the beginning of list *)
"shashank" :: names; 

Append element to the beginning of list with "::" constructor called "cons" operator

Tuples

Values inside a tuple can have varying types.  Optionally enclosed in parentheses, separated by commas.

ocaml

let tup = (1, 5, "Singapore", 15.5);

(* Parentheses is optional *)
let tup = 1, 5, "Singapore", 15.5;

Named Tuples / Records

Same as tuples, they have names of each variable. Also, called as records

ocaml

let record = {num: float; denom: int};

Arrays

They have values of one type enclosed within "[| |]". Arrays are mutable data types.

ocaml

let arr = [| 1;2;3; |];

arr.(0);
=> :int = 1
arr.(2);
=> :int = 3

An array can directly access any item with an index.

Strings & Characters

Strings should be enclosed in double quotes, while characters in single quotes.

Strings are not arrays of chars and hence should not be mixed together in an expression.

ocaml

let a = "shashank";
let a = 's';

Variants

Another powerful feature of Ocaml is variants. These are user-defined data types. It represents a value whose DS can be one of the several possibilities. It is similar to Enums in Python or Java with more features. 

ocaml

(* Represents possible values of lang *)
type lang = Ocaml | Python | Java;

let x = Ocaml;
=> val x : lang = Ocaml 
(* x is a type of lang *)

(* You can define much more complex constructors *)
type shape = Circle of float | Rectangle of int * int | Square of int;
let circle = Circle 5.;;
=> val circle : shape = Circle 5. 
(* Ocaml will infer type of circle as shape *)

Each value in the variant is called a constructor.

Another important example of variants is the built-in 'a option type.

ocaml

type 'a option = Some of int | None;

'a will either have some value of type int or absence of value i.e. None.

The most common usage of variant types is to describe recursive data structures like tree

ocaml

type 'a btree = Empty | Node of 'a * 'a btree * 'a btree;

One crucial feature of Ocaml is there is no null data type, thus avoiding the Billion Dollar mistake. There will always be some value of absence of value represented by None.

Pattern Matching

One of the most important and awesome features of Ocaml is pattern matching. In simple terms, it's very similar to switch case constructs in other languages but much more powerful. 

Can match an argument against:

  • Exact Value
  • Predicate
  • Type constructor

ocaml

let is_zero y = match y with 
| 0 -> true
| _ -> false
;

let is_even x = match x with 
| x when x mod 2 = 0 -> true
| _ -> false
;

let calculate_area shape = match shape with
| Rectange (l,b) -> l * b
| Square x -> x * x
| Circle r -> int_of_float (3.14 *. r *. r)
;;

Exception Handling 

Users can define exceptions with the "exception" keyword.

ocaml

exception EmptyList;

let find_first arr = match arr with 
| [] -> raise EmptyList
| h :: _ -> h
;

find_first [];
=> Exception: EmptyList 

Lazy Expressions

You can delay the evaluation of an expression when you actually need it. This is similar to generators in Python and sequences in Clojure. Lazy.force  will evaluate the expression. 

ocaml

let add a b = lazy (print_endline "Lazy evaluation"; a + b);

=> : int lazy_t = <lazy>

Lazy.force (add 2 3);
=> : int = 5

(* This will evaluate the expression *)


More details & solutions for a few exercises are here.