Homepage / Notes / Computer Science / Programming Languages / OCaml
Garbage collection, static type-checking (with type inference), immutable programming, pattern matching.
ocamlc
bytecode compilerocamlopt
native code compilerOpam is OCaml's package manager.
eval $(opam env)
in new shells to activate Opam.
opam init
to initialize ~/.opam
opam search {query}
to search for package {query}
.opam show {package}
to display information about {package}
.opam install x
to install x
package.opam update
to update package list.opam upgrade
to upgrade installed packages to their latest version.Dune is OCaml's build system.
dune init proj helloworld
to init helloworld
project.dune build
to build.dune exec helloworld
to build & run.Can just type ocaml
to access a REPL. But an easier-to-use version is utop
.
Basic setup in ~/.ocamlinit
to load packages every time utop
is started:
#use "topfind";;
#thread;;
(* this is a comment *)
The basic types in OCaml are:
OCaml type | Range |
---|---|
'a | Any type |
int | 63-bit signed int on 64-bit processors, or 31-bit signed int on 32-bit processors |
float | IEEE double-precision floating point, equivalent to C's double |
bool | A boolean, written either 'true' or 'false' |
char | An 8-bit character |
string | A string (sequence of 8 bit chars) |
type colour = Red | Green | Blue
type colour = Red | Green | Blue
let l = [Red; Green; Green]
val l : colour list = [Red; Green; Green]
let l = [Red; Green; Yellow]
Line 1, characters 21-27:
1 | let l = [Red; Green; Yellow];;
^^^^^^
Error: This variant expression is expected to have type colour
There is no constructor Yellow within type colour
type fruit = Banana | Strawberry | Apple;;
let get_colour = function
"yellow"
| Banana -> "red"
| Strawberry -> "green";;
| Apple ->
get_colour Apple;;
green
https://www.janestreet.com/tech-talks/unboxed-types-for-ocaml/
1 + 1
2
For floats, +.
have to be used, as well as x.
for the number to be added
1.5 +. 1.
2.5
Underscores (_
) can be used to help read large numbers
2_000_000 / 20_000
100
Notice the difference between 1 / 3
and 1. /. 3.
:
1 / 3
0
1. /. 3.
0.33333333333333331
Variable names must start with a lowercase letter or an underscore.
let x = 3 + 4;;
7
let y = x + x;;
14
Can be used to define multiple variables at the same time:
let x, y = 8, 9;;
val x : int = 8
val y : int = 9
Chars use single-quotes:
'd';;
- : char = d
https://ocaml.org/api/String.html
Strings use double-quotes:
let name = "Damien";;
val name : string = "Damien"
Strings are essentially lists of characters
0];; name.[
- : char = 'D'
1];; name.[
- : char = 'a'
String.length name;;
- : int = 6
^
is used to concatenate strings.
"Hello, " ^ name;;
"Hello, Damien"
String.starts_with ~prefix:"Dam" name;;
- : bool = true
String.starts_with ~prefix:"ien" name;;
- : bool = false
String.ends_with ~suffix:"ien" name;;
- : bool = true
String.ends_with ~suffix:"Dam" name;;
- : bool = false
let plus x y = x + y;;
2 3;; plus
5
Example of partial application:
let plus_two = plus 2;;
3;; plus_two
5
let square x = x * x;;
3 square
9
let ratio x y = Float.of_int x /. Float.of_int y;;
1 3;; ratio
0.33333333333333331
using stdlib
List.map (fun x -> x * 2) [1; 2; 3];;
- : int list = [2; 4; 6]
using Base
from Jane Street
List.map [1; 2; 3] ~f:(fun x -> x*2);;
- : int list = [2; 4; 6]
Ordered collection of values that can each be of a different type.
let tuple_a = (9, "nine");;
val tuple_a : int * string = (9, "nine")
let tuple_b = (9, "nine", 9.);;
val tuple_b : int * string * float = (9, "nine", 9.)
Values can be extracted from the tuple by using pattern matching:
let (x,y) = tuple_a;;
val x : int = 9
val y : string = "nine"
String.length y;; x +
- : Base.Int.t = 13
https://ocaml.org/api/List.html
Any number of (ordered) items of the same type.
let countries = ["United States"; "France"; "Canada"]
val countries : string list = ["United States"; "France"; "Canada"]
Mixing types is not possible in lists:
let numbers = [1;"two";3]
Line 1, characters 17-22:
1 | let numbers = [1;"two";3];;
^^^^^
Error: This expression has type string but an expression was expected of type
int
Because commas are reserved to separate elements of tuples, using them in Lists returns a tuple inside a list:
"OCaml", "Python", "Ruby"];; [
- : (string * string * string) list = [("OCaml", "Python", "Ruby")]
Even without parentheses, commas create a tuple:
1,2,3;;
- : int * int * int = (1, 2, 3)
Getting the length of a list:
List.length countries;;
3
List.nth ["a"; "b"; "c"] 2;;
c
Short for "member of" list
List.mem "France" countries;;
- : bool = true
List.mem "China" countries;;
- : bool = false
Prepending to a list:
"Germany" :: "Spain" :: countries;;
- : string list = ["Germany"; "Spain"; "United States"; "France"; "Canada"]
Note the initial list is unchanged:
countries;;
- : string list = ["United States"; "France"; "Canada"]
1; 2; 3] @ [4; 5; 6];; [
- : int Base.List.t = [1; 2; 3; 4; 5; 6]
Compiler warns us that the code below is incomplete, because it doesn't support the case where countries is an empty list.
let favourite :: the_rest = countries;;
Line 1, characters 4-25:
1 | let favourite :: the_rest = countries;;;;
^^^^^^^^^^^^^^^^^^^^^
Warning 8: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
[]
val favourite : string = "United States"
val the_rest : string list = ["France"; "Canada"]
Using match
instead:
let my_favourite_country countries =
match countries with
| first :: the_rest -> first"Canada"
| [] -> ;;
val my_favourite_country : string list -> string = <fun>
my_favourite_country countries;;
United States
my_favourite_country [];;
Canada
List.iter print_endline ["a"; "b"; "c"];;
a
b
c
- : unit = ()
map
over list:
List.map String.length countries;;
- : int list = [13; 6; 6]
Using StdLabels
:
open StdLabels;;
List.map ~f:String.length countries;;
- : int list = [13; 6; 6]
Called zip
in most other languages?
List.map2 ( + ) [1; 2; 3] [4; 5; 6];;
- : int list = [5; 7; 9]
Returns first element given predicate:
List.find (fun x -> x > 2) [1; 2; 3; 4; 5];;
3
Returns all element given predicate:
List.filter (fun x -> x > 2) [1; 2; 3; 4; 5];;
- : int list = [3; 4; 5]
Comparison feature compare
can be used:
List.sort compare [3; 4; 1; 2; 4; 5];;
- : int list = [1; 2; 3; 4; 4; 5]
Fun.flip
flips the arguments of a binary function, meaning x < y will become y < x:
List.sort (Fun.flip compare) [3; 4; 1; 2; 4; 5];;
- : int list = [5; 4; 4; 3; 2; 1]
List.fold_left ( + ) 0 [1; 2; 3];;
6
List.fold_left ( - ) 0 [1; 2; 3];;
-6
Accumulator is placed after the list:
List.fold_right ( + ) [1; 2; 3] 0;;
6
List.fold_right ( - ) [1; 2; 3] 0;;
2
If you also need elements which tested false:
List.partition (fun x -> x > 2) [1; 2; 3; 4; 5];;
- : int list * int list = ([3; 4; 5], [1; 2])
let rec sum l =
match l with
0
| [] ->
| hd :: tl -> hd + sum tl ;;
val sum : Base.Int.t list -> Base.Int.t = <fun>
1;2;3];; sum [
6
Simplistic dictionary data structure:
let numbers = [(1, "one"); (2, "two"); (3, "three"); (4, "four"); (5, "five")];;
val numbers : (int * string) list =
[(1, "one"); (2, "two"); (3, "three"); (4, "four"); (5, "five")]
List.assoc 3 numbers;;
- : string = "three"
List.mem_assoc 3 numbers;;
- : bool = true
List.mem_assoc 6 numbers;;
- : bool = false
List.split numbers;;
- : int list * string list =
([1; 2; 3; 4; 5], ["one"; "two"; "three"; "four"; "five"])
List.combine [1; 2; 3; 4; 5] ["one"; "two"; "three"; "four"; "five"];;
- : (int * string) list =
[(1, "one"); (2, "two"); (3, "three"); (4, "four"); (5, "five")]
type point2d = { x : float; y : float }
type point2d = { x : Base.float; y : Base.float; }
let p = { x = 3.; y = -4. };;
val p : point2d = {x = 3.; y = -4.}
let magnitude { x = x_pos; y = y_pos } =
sqrt (x_pos **. 2. +. y_pos **. 2.)
Float. ;;
val magnitude : point2d -> Base.Float.t = <fun>
Using field punning for a more terse definition:
let magnitude { x; y } = Float.sqrt (x **. 2. +. y **. 2.);;
val magnitude : point2d -> Base.Float.t = <fun>
You can re-use types as components of larger types:
type circle_desc = { center: point2d; radius: float }
type circle_desc = { center : point2d; radius : Base.float; }
module Names = Map.Make(String);;
module Names :
sig
type key = String.t
type 'a t = 'a Map.Make(String).t
val empty : 'a t
val is_empty : 'a t -> bool
val mem : key -> 'a t -> bool
val add : key -> 'a -> 'a t -> 'a t
val update : key -> ('a option -> 'a option) -> 'a t -> 'a t
val singleton : key -> 'a -> 'a t
val remove : key -> 'a t -> 'a t
val merge :
(key -> 'a option -> 'b option -> 'c option) -> 'a t -> 'b t -> 'c t
val union : (key -> 'a -> 'a -> 'a option) -> 'a t -> 'a t -> 'a t
val compare : ('a -> 'a -> int) -> 'a t -> 'a t -> int
val equal : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool
val iter : (key -> 'a -> unit) -> 'a t -> unit
val fold : (key -> 'a -> 'b -> 'b) -> 'a t -> 'b -> 'b
val for_all : (key -> 'a -> bool) -> 'a t -> bool
val exists : (key -> 'a -> bool) -> 'a t -> bool
val filter : (key -> 'a -> bool) -> 'a t -> 'a t
val filter_map : (key -> 'a -> 'b option) -> 'a t -> 'b t
val partition : (key -> 'a -> bool) -> 'a t -> 'a t * 'a t
val cardinal : 'a t -> int
val bindings : 'a t -> (key * 'a) list
val min_binding : 'a t -> key * 'a
val min_binding_opt : 'a t -> (key * 'a) option
val max_binding : 'a t -> key * 'a
val max_binding_opt : 'a t -> (key * 'a) option
val choose : 'a t -> key * 'a
val choose_opt : 'a t -> (key * 'a) option
val split : key -> 'a t -> 'a t * 'a option * 'a t
val find : key -> 'a t -> 'a
val find_opt : key -> 'a t -> 'a option
val find_first : (key -> bool) -> 'a t -> key * 'a
val find_first_opt : (key -> bool) -> 'a t -> (key * 'a) option
val find_last : (key -> bool) -> 'a t -> key * 'a
val find_last_opt : (key -> bool) -> 'a t -> (key * 'a) option
val map : ('a -> 'b) -> 'a t -> 'b t
val mapi : (key -> 'a -> 'b) -> 'a t -> 'b t
val to_seq : 'a t -> (key * 'a) Seq.t
val to_rev_seq : 'a t -> (key * 'a) Seq.t
val to_seq_from : key -> 'a t -> (key * 'a) Seq.t
val add_seq : (key * 'a) Seq.t -> 'a t -> 'a t
val of_seq : (key * 'a) Seq.t -> 'a t
end
Create an empty Names
map n
:
let n = Names.empty;;
val n : 'a Names.t = <abstr>
Add some data, by overwriting previous n
:
let n = Names.add "Damien" "Gonot" n;;
val n : string Names.t = <abstr>
And more:
let n = Names.add "John" "Doe" n;;
let n = Names.add "Hercules" "Poirot" n;;
val n : string Names.t = <abstr>
let print_name first_name last_name =
print_endline(first_name ^ " " ^ last_name);;
val print_name : string -> string -> unit = <fun>
Names.iter print_name n;;
Damien Gonot
Hercules Poirot
John Doe
- : unit = ()
"Damien" n;; Names.find
- : string = "Gonot"
module StringSet = Set.Make(String);;
module StringSet :
sig
type elt = String.t
type t = Set.Make(String).t
val empty : t
val is_empty : t -> bool
val mem : elt -> t -> bool
val add : elt -> t -> t
val singleton : elt -> t
val remove : elt -> t -> t
val union : t -> t -> t
val inter : t -> t -> t
val disjoint : t -> t -> bool
val diff : t -> t -> t
val compare : t -> t -> int
val equal : t -> t -> bool
val subset : t -> t -> bool
val iter : (elt -> unit) -> t -> unit
val map : (elt -> elt) -> t -> t
val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a
val for_all : (elt -> bool) -> t -> bool
val exists : (elt -> bool) -> t -> bool
val filter : (elt -> bool) -> t -> t
val filter_map : (elt -> elt option) -> t -> t
val partition : (elt -> bool) -> t -> t * t
val cardinal : t -> int
val elements : t -> elt list
val min_elt : t -> elt
val min_elt_opt : t -> elt option
val max_elt : t -> elt
val max_elt_opt : t -> elt option
val choose : t -> elt
val choose_opt : t -> elt option
val split : elt -> t -> t * bool * t
val find : elt -> t -> elt
val find_opt : elt -> t -> elt option
val find_first : (elt -> bool) -> t -> elt
val find_first_opt : (elt -> bool) -> t -> elt option
val find_last : (elt -> bool) -> t -> elt
val find_last_opt : (elt -> bool) -> t -> elt option
val of_list : elt list -> t
val to_seq_from : elt -> t -> elt Seq.t
val to_seq : t -> elt Seq.t
val to_rev_seq : t -> elt Seq.t
val add_seq : elt Seq.t -> t -> t
val of_seq : elt Seq.t -> t
end
let s = StringSet.singleton "hello";;
val s : StringSet.t = <abstr>
let s = List.fold_right StringSet.add ["world"; "stranger"] s;;
val s : StringSet.t = <abstr>
fun str -> print_endline str) s;; StringSet.iter (
hello
stranger
world
- : unit = ()
"stranger" s; StringSet.mem
- : bool = true
1000
is the initial size of the hash table. Hash tables can grow further is size has been underestimated.
let my_hash = Hashtbl.create 1000;;
val my_hash : ('_weak1, '_weak2) Hashtbl.t = <abstr>
'_weak1
is the type for the key and '_weak2
the type for the value
There are no concrete types yet. The underscore indicates that once the key and value types will be chosen, they'll be fixed.
Hash tables are updated in-place, adding a new member doesn't return a new hash table like maps do.
Hashtbl.add my_hash "h" "hello";;
Hashtbl.add my_hash "h" "harbour";;
Hashtbl.add my_hash "w" "world";;
Hashtbl.add my_hash "w" "win";;
Hashtbl.add my_hash "w" "wonderful";;
- : unit = ()
Types are fixed now:
my_hash;;
- : (string, string) Hashtbl.t = <abstr>
Hashtbl.find
returns the last added element:
Hashtbl.find my_hash "h";;
- : string = "harbour"
To find all elements:
Hashtbl.find_all my_hash "w";;
- : string list = ["wonderful"; "win"; "world"]
Instead of using Hashtbl.add
, we can use Hashtbl.replace
if we only want one value per key:
Hashtbl.replace my_hash "t" "try";;
Hashtbl.replace my_hash "t" "test";;
Hashtbl.find_all my_hash "t";;
- : string list = ["test"]
Hashtbl.remove my_hash "t";;
Hashtbl.find my_hash "t";;
Exception: Not_found.
let z = 7 in
z + z ;;
14
The scope of the let
binding is terminated by the ;;
, value of z
is no longer available outside that scope:
z;;
Line 1, characters 0-1:
1 | z;;;;
^
Error: Unbound value z
Those can be nested:
let x = 7 in
let y = x * x in
x + y ;;
56
let max a b =
if a > b then a else b;;
val max : 'a -> 'a -> 'a = <fun>
max 9 10;;
10
Used to express that a value might or might not be present.
let divide x y =
if y = 0 then None else Some (x / y)
;;
val divide : int -> int -> int option = <fun>
Values can't be null
in OCaml. Missing values are explicit. If you want to allow some data to be absent, you have to use Options.
|>
pipes work!
1; 2; 3] |> List.map (fun x -> x * 2) [
- : int list = [2; 4; 6]
Multiple pipes!
1; 2; 3] |> List.map (fun x -> x * 2) |> List.fold_left (+) 0 [
- : int = 12
@@
is like a reverse pipe
List.map (fun x -> x * 2) @@ [1; 2; 3]
- : int list = [2; 4; 6]
OCaml is mostly pure and functional. Almost all data structures are immutable. But OCaml does support imperative programming.
let numbers = [| 1; 2; 3; 4; 5 |];;
val numbers : int array = [|1; 2; 3; 4; 5|]
.(i)
is used to access an element of an array by index i
. The <-
syntax is used for modification.
2) <- 4;; numbers.(
- : unit = ()
Because the elements of the array are counted starting from zero, .i(2)
is the third element.
numbers;;
- : int array = [|1; 2; 4; 4; 5|]
let x = { contents = 0 };;
val x : int ref = {contents = 0}
1;; x.contents <- x.contents +
- : unit = ()
x;;
- : int ref = {contents = 1}
let x = ref 0;;
val x : int ref = {contents = 0}
!x;;
- : int = 0
1;; x := !x +
- : unit = ()
!x;;
- : int = 1
let sum list =
let sum = ref 0 in
List.iter (fun x -> sum := !sum + x) list;
!sum ;;
val sum : int list -> int = <fun>
5; 5; 5];; sum [
- : int = 15
for i = 1 to 5 do
print_endline (string_of_int i)
done
1
2
3
4
5
- : unit = ()
for i = 5 downto 1 do
print_endline (string_of_int i)
done
5
4
3
2
1
- : unit = ()
Have to use refs
in order to be able to quit the loop
let quit_loop = ref false in
while not !quit_loop do
print_endline "this will print once";
true
quit_loop := done
this will print once
- : unit = ()
Module names always start with an uppercase letter.
let ratio x y =
let open Float.O in
of_int x / of_int y ;;
val ratio : int -> int -> Base.Float.t = <fun>
More concise syntax:
let ratio x y =
Float.O.(of_int x / of_int y) ;;
val ratio : int -> int -> Base.Float.t = <fun>
https://ocaml.org/learn/tutorials/file_manipulation.html
https://ocaml.janestreet.com/ocaml-core/v0.12/doc/base/index.html
An addition to the standard library developed by Jane Street.
https://aantron.github.io/dream/
Tidy, feature-complete Web framework
https://github.com/tmattio/dream-cli
https://github.com/xhtmlboi/yocaml
YOCaml is a static site generator, mostly written in OCaml
https://github.com/janestreet/bonsai
Bonsai is a library for building interactive browser-based UI.
https://github.com/paurkedal/ocaml-caqti
Cooperative-threaded access to relational data
kind of like Python's SQLAlchemy or Java's JDBC
https://medium.com/@bobbypriambodo/interfacing-ocaml-and-postgresql-with-caqti-a92515bdaa11
https://ceramichacker.com/blog/28-2x-backend-webdev-w-dream-and-caqti
https://github.com/Gopiandcode/petrol
Petrol is a Free software library that provides a high-level OCaml API for interacting with SQL databases. The aim of this interface is to provide a type-safe API to allow developers to define their SQL tables and queries directly in OCaml, thereby avoiding the impedence mismatch and fragility that comes with having to directly write SQL code, as is typical in a normal Caqti-based project.
A distributed database built on the same principles as Git
https://github.com/rgrinberg/opium
Sinatra like web toolkit for OCaml
https://github.com/leostera/minttea
A fun little TUI framework for OCaml
https://github.com/tmattio/spin
OCaml project generator.
https://github.com/melange-re/melange
A mixture of tooling combined to produce JavaScript from OCaml & Reason
https://v2.ocaml.org/api/index.html
https://v2.ocaml.org/releases/4.14/htmlman/index.html
https://dev.realworldocaml.org/
Heavily uses Jane Street packages
https://johnwhitington.net/ocamlfromtheverybeginning/index.html
https://baturin.org/docs/ocaml-faq/
https://github.com/kayceesrk/ocaml5-tutorial/
Realtime grep for OCaml sources available on opam