Benefits of OCaml

Type system

One of the most important features of OCaml is its type system.

Strongly typed

The types of all values in an OCaml program are checked to make sure that they are used appropriately. Any inappropriate use (a type mismatch) incurs an error. This removes an important class of bugs that occur when a value of one type is incorrectly treated as a value of another type, which would otherwise produce nonsensical results.

As an important part of a safe programming language, strong typing improves program reliability and reduces development time by highlighting type mismatches.

Statically typed

Not only does the OCaml programming language require that all types are checked, this checking is done entirely at compile time. Many languages (such as Lisp) unnecessarily defer type checking to run-time. Thus, a dynamically-typed program with a simple type error must be run for an arbitrarily long time before a run-time type error is produced and the bug can be fixed. In contrast, compilers of statically typed languages like OCaml detect all such errors before the program is run.

Programmers using dynamically typed languages like Lisp typically resort to unit testing in order to gain sufficient confidence that run-time type errors will not occur in practice. In contrast, static type checking formally proves that there are no sources of run-time type errors. An important advantage of static type checking is that run-time type checks are no longer necessary and can be removed, greatly improving program performance. Once a programmer is familiar enough with OCaml's type system they can leverage it to prove that the removal of run-time checks is safe.

Despite advancements in static type checking, a significant number of programmers continue to prefer dynamic type checking. We have collated some of the reasons here.

Static type checking highlights an important class of errors at compile time and improves run-time performance.

Type inference

Most conventional statically-typed programming languages (e.g. C, C++, C#, Java) require the programmer to repeatedly restate the types of expressions. For example, the following Java code instantiates a list of strings and populates it with two elements:

LinkedList<String> m = new LinkedList<String>();
m.add("elt1");
m.add("elt2");

Restating types is wasted effort and, more importantly, serves to bloat the source code of a program, making it more difficult to navigate, develop and maintain.

In OCaml, all programs can be written such that the types they use are competely inferred, i.e. it is never necessary to explicitly define and declare types in OCaml. However, defining important types in a program is a good way to leverage static type checking by providing machine-checked documentation, improving error reporting and tightening the type system to catch more errors.

The above Java code can be written much more succinctly in OCaml as ["elt1"; "elt2"]:

# ["elt1"; "elt2"];;
- : string list = ["elt1"; "elt2"]

OCaml's type system is also much more expressive than Java's. For example, given the function fun n -> n+1:

# fun n -> n+1;;
succ : int -> int = <fun>

OCaml infers that the function maps integers onto integers even though no types were declared. The inference was based upon the use of the integer operator + and the integer literal 1.

Here are some of the basic built-in types being inferred:

# (1, 2.3, 'a', "foo", [7], [|7|], Some 7);;
- : int * float * char * string * int list * int array * int option =
(1, 2.3, 'a', "foo", [7], [|7|], Some 7)

Polymorphic functions:

# let rec apply2 f x = f(f x);;
val apply2 : ('a -> 'a) -> 'a -> 'a = <fun>

We have polymorphic classes, class types and objects. For example, calling the name method of an object o as o#name requires that o implements at least this method:

# let f o =
    print_endline o#name;;
val f : < name : string; .. > -> unit = 

We also have polymorphic variants (inferrred sum types). For example, simply writing a recursive evaluator allows the type of the abstract syntax tree to be inferred:

# let rec eval = function
    | `Int n -> n
    | `Add(e1, e2) -> eval e1 + eval e2
    | `Mul(e1, e2) -> eval e1 * eval e2;;
val eval :
  ([< `Add of 'a * 'a | `Int of int | `Mul of 'a * 'a ] as 'a) -> int = <fun>

where the inferred type variable 'a denotes an expression.

Type inference is one of the main sources of OCaml's brevity. More importantly, the inferred types of subexpressions can be made available within the development environment (type throwback), allowing the programmer to inspect them easily.