← Back to the lab

Problem 1: Pattern matching basics

For no_empty_cells_left.

match in one line

match v with | pattern -> expr | pattern -> expr | ... picks the first pattern that matches v and evaluates the expression next to it. Patterns are tried top to bottom. The first one that fits wins.

let classify n =
  match n with
  | 0 -> "zero"
  | 1 -> "one"
  | _ -> "many"

The three simplest patterns:

Recursing over a list

A list is built from two constructors: [] for empty, h :: t for a head h and tail t. A function over a list almost always has one match clause per constructor, with the recursive call made on the strictly smaller tail.

let rec length l =
  match l with
  | []     -> 0            (* base case *)
  | _ :: t -> 1 + length t (* recursive case *)

let _ = length [10; 20; 30]  (* = 3 *)

Local functions

The right-hand side of a let ... in binding can be any expression. A function is just another kind of value, no different from an int or a string. let name args = expr in body names a function the same way let x = 5 in body names a number. name exists only inside body.

let describe n =
  let is_even m = m mod 2 = 0 in
  if is_even n then "even" else "odd"

This is the same nesting as let x = 5 in let y = 10 in x + y. One binding's body is itself another let. Here the inner binding's value happens to be the function is_even. is_even can take several arguments, and can be let rec if it calls itself. It's still just a name, scoped inside describe.

Why a helper function?

Sometimes the function you're implementing takes extra arguments, or a different shape, than the thing you actually want to recurse over. A small let rec helper ... = match ... with ... defined inside the main function lets you recurse on just the list, while the outer function keeps whatever signature it needs.