zettelkasten

Search IconIcon to open search
Dark ModeDark Mode

Speedrunning OCaml

Date: 28 Jul 2024

#post #ocaml #functional-programming #programming-language #guide

One file going over OCaml essentials, inspired by Hype for Types’s 15-150 crash course. We assume some knowledge of Standard ML and Rust here.

(* == COMMENTS ==
  just do SML comments :)
*)

(* blah blah blah *)
(* blah blah blah (* nested blah blah blah *) *)


(* == VALUE DECLARATIONS ==
  - instead of using `val`, use `let` 
  - tuples work without parens
  - replace `real` with `float`
  note tuple evaluation is right to left
*)

let x : int = 1
let x : string = "hello"
let x : bool = false
let x : char = 'x'
let x : float = 3.14159
let x : int * int * string = (1, 2 + 3, "world")
let x : int * int * string = 1, 2 + 3, "world"


(* == BUILT IN OPERATIONS == 
  - use `(` `)` to make operation a function
  - use `.` after int operations to make it float operation
  - `-` is polymorphic for `float` and `int`
    - `~-` is the `int` only version
    - `~-.` is the `float` only version
*)

let 11 = 5 + 6
let 2 = 5 mod 3
let true = (5 + 6) = (+) 5 6
let r : float = 3.14159 *. 2.0 +. 3.0 /. 34.0


(* == EXPRESSIONS ==
  they should work as expected
*)

let r : int = 2 * 2 + 3
let r : string = "hello" ^ " " ^ "world"
let r : string = if -1 < 0 then "reasonable" else "absurd"


(* == FUNCTION DECLARATIONS ==
  - instead of using `fun`, use `let` or `let rec` 
  - use `let rec` for recursive function
  - curry works as expected 
  - function clauses do not exist, use `match` instead
  - `and` still works for mutual recursion
  - use `(` `)` to define infix functions
*)

let incr (x : int) : int = x + 1
let incr x = x + 1

let rec loop x = loop (x + 1)

let add x y = x + y

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

let rec fact n = match n with
  | 0 -> 1
  | n -> n * fact (n - 1)

let rec 
  is_divisible_by_three = function | 0 -> true | n -> is_not_divisible_by_three (n - 1)
and 
  is_not_divisible_by_three = function | 0 -> false | 1 -> false | n -> is_divisible_by_three (n - 2)

let (>>=) o f = match o with
  | None -> None
  | Some x -> f x


(* == LAMBDA FUNCTIONS ==
  - instead of `fn`, write `fun` or `function`
  - use `fun` to curry
  - use `function` to pattern match
  - replace `=>` with `->`
*)

let square = function x -> x * x

let mult = fun x y -> x * y

let rec fact = function | 0 -> 1 | n -> n * fact (n - 1)


(* == LET IN END ==
  the OCaml version:
  - does not have `end`
  - keep nesting instead of having multiple declarations between `let` and `in`
*)

let r : int = 
  let a = 1 in
  let b = a * 2 in
  let c = a + b in
  a + b + c


(* == PATTERN MATCHING ==
  - add `|` before the first clause
  - replace `=>` with `->`
  - pattern with conditional works like in Rust, just replace `if` with `when`
  - alias pattern using `as`
*)

let r: string * int = match Some 3 with
  | None -> ("nothing", -1)
  | Some x when x = 3 -> ("something exactly 3", x)
  | Some x -> ("something not 3", x)

let p = (2, 3)
let ((x, y) as p') = p


(* == LISTS  ==
  - use `;` instead of `,`
  - `::` and `@` should work as expected
  - `nil` won't work
  note:
  - `,` would make a tuple, even without parens, somewhat like python
*)

let l : int list = [150; 210; 122; 251; 451]
let l : (int * int) list = [15, 150; 15, 210; 15, 122; 15, 251; 15, 451]
let l : int list = 4::3::[]
let l : int list = [1; 2] @ [3; 4]

(* == DATATYPES ==
  - replace `datatype` with `type`
  - first letter of constructor must be uppercase
  - add `|` before first constructor
  notes:
  - `type` aliases still work
  - constructors cannot be passed in as if they are functions, use lambda instead
  - the built in `option` constructors are `Some` and `None`
*)

type 'a another_option = | Nothing | Something of 'a
type 'a tree = | Empty | Node of 'a tree * 'a * 'a tree
type 'a labelled_rose = | Rose of 'a * string * 'a labelled_rose list
type point = int * int


(* == POLYMORPHISM ==
  they should work as expected
*)

(* 'a -> 'b *)
let rec loop x = loop x 

(* 'a * 'b -> 'a *)
let fst (x, y) = x

(* 'a list -> ('a -> 'b) -> 'b list *)
let rec map l f =
  match l with
    | [] -> []
    | x::xs -> (f x)::(map xs f)


(* THE HOFS
  find some of them in the `List` module
*)

(* 'a list -> int -> 'a *)
let nth = List.nth

(* ('a -> 'b) -> 'a list -> 'b list *)
let map = List.map

(* ('a -> bool) -> 'a list -> 'a lists *)
let filter = List.filter


(* ARRAYS
  whatever this is
*)

let arr: int array = [|150; 210; 122; 251; 451|]
let 150 = arr.(0)


(* == SIDE EFFECTS AND IMPERATIVE ==
  - use `print_endline : string -> unit` to print
  - use `;` to chain together multiple things that return unit
  - use `[@@deriving show]` to do something like deriving a display trait
  - useful things to derive: `show`, `ord`, `hash`, `eq`
*)

let () = print_string "hello world\n"
let () = print_endline "hello world"
let () = Printf.printf "%s%d%s%c" "this is a number: " 42 "; and this is a char: " 'c'

let print_and_pass_through x =
  print_endline x;
  x

(* this generates `show_tree : tree -> string` and `tree_show : tree -> string`! *)
type tree = | Leaf of int | Node of tree * tree [@@deriving show]

let t = Node(Node(Leaf(2), Leaf(3)), Leaf(4))
let () = print_endline (tree_show t);


(* == NAMED AND OPTIONAL ARGUMENTS ==
  - put `~` in front of argument that shall be named
  - when applying function, pass `variable` to `name` by `~name:variable`
  - a shorthand of `~x:x` is `~x`
  - a unit argument is needed for functions taking optional arguments
*)

let takeslotsofarguments ~a ~b ~c ~d = ((a + b) * c) / d

let _ = takeslotsofarguments ~a:3 ~c:5 ~d:8 ~b:2

let _ = 
  let a = 3 in
  let c = 5 in
  takeslotsofarguments ~a ~c ~d:8 ~b:2

let allargsoptional ?(a = 3) ?(b = 2) () = a + b

let _ = allargsoptional ()
let _ = allargsoptional ~a:3 ()

let _ = 
  let b = 4 in
  allargsoptional ~b ()


(* == RECORD TYPES ==
  - use `;` to separate fields
  - record types must be declared
  - get things out buy `.` or pattern matching similar to Rust
  - functional record updates using `with`
*)

type brewer = | V60 | Frenchpress | Espressomachine | Aeropress | Siphon | Coldbrew | Mokapot
type brew = {
  coffee_mass: float;
  grind_setting: int;
  water_mass: float;
  brew_time: int;
  brewer: brewer;
}

let b = {coffee_mass = 16.0; grind_setting = 182; water_mass = 240.0; brew_time = 180; brewer = V60;}

let g = b.grind_setting
let {grind_setting = g; _} = b

let b' = {b with brewer = Frenchpress}

References: