Range Over Iterators in OCaml

Starting with version 1.23, there is support for iterators, which lets us range over pretty much anything!

Let’s look at the List type from the previous example again. In that example, we had an AllElements method that returned a list of all elements in the list. With enumerators, we can do it better - as shown below.

type 'a element = {
    mutable next: 'a element option;
    val: 'a;
}

type 'a list = {
    mutable head: 'a element option;
    mutable tail: 'a element option;
}

let create_list () = {
    head = None;
    tail = None;
}

let push lst v =
  let new_element = { next = None; val = v } in
  match lst.tail with
  | None ->
      lst.head <- Some new_element;
      lst.tail <- Some new_element;
  | Some tail_element ->
      tail_element.next <- Some new_element;
      lst.tail <- Some new_element

let iter_list lst yield =
  let rec aux e =
    match e with
    | None -> ()
    | Some element ->
        if yield element.val then aux element.next
  in
  aux lst.head

All returns an enumerator, which in OCaml is usually achieved with generator-like functions.

The enumerator function takes another function as a parameter, called yield by convention (but the name can be arbitrary). It will call yield for every element we want to iterate over, and note yield’s return value for a potential early termination.

let all_elements lst =
  fun yield ->
    iter_list lst yield

Iteration doesn’t require an underlying data structure, and doesn’t even have to be finite! Here’s a function returning an enumerator over Fibonacci numbers: it keeps running as long as yield keeps returning true.

let generate_fib () =
  fun yield ->
    let rec aux a b =
      if yield a then aux b (a + b)
    in
    aux 1 1

The main function demonstrates how to push values to the list and how to use the enumerator.

let () =
  let lst = create_list () in
  push lst 10;
  push lst 13;
  push lst 23;

  (* Using the enumerator *)
  let all = all_elements lst in
  all (fun v -> print_endline (string_of_int v); true);

  (* Collecting values into a list *)
  let all_values = ref [] in
  all (fun v -> all_values := v :: !all_values; true);
  print_endline ("all: [" ^ (String.concat " " (List.rev_map string_of_int !all_values)) ^ "]");

  (* Fibonacci example *)
  let fib = generate_fib () in
  try
    fib (fun n ->
      if n >= 10 then raise Exit;
      print_endline (string_of_int n);
      true
    )
  with Exit -> ()

Output:

10
13
23
all: [10 13 23]
1
1
2
3
5
8