Synechepedia

Lazy Evaluation in Ocaml: A Cheat Sheet

Notes to remind a forgetful OCaml user of the ways of laziness.

Basic concepts

Skip this if you’re just looking for recipes and a refresher on use

The Lazy Type and its Values: lazy_t, Lazy.t, and lazy

OCaml has built in support for memoized lazy evaluation. Three artifacts are all we need to take advantage of this feature:

'a lazy_t
the internal representation of a deferred evaluation which, when forced, will reduce to a value of type 'a.
'a Lazy.t
the user-level type constructors to specify the type of deferred evaluations reducing to values of type 'a.
lazy
A keyword functioning as a value constructor that takes values of type 'a to values of type 'a lazy_t.
     utop # lazy 3;;
     - : int lazy_t = lazy 3

     utop # type t = T of int Lazy.t;;
     type t = T of int lazy_t

     utop # T (lazy 3);;
     - : t = T (lazy 3)

Lazy values and thunks

'a Lazy.tunit -> 'a

OCaml’s Lazy values are akin to thunks (deferred evaluations by means of nullary functions), but they are not equivalent.

That is, given

     let a = lazy 3
     let b = fun () -> 3

it’s true that

     let () = assert (force a = b ())

However, because OCaml’s built-in laziness memoizes the results of forced evaluation, side effecting behavior can alter the computations that use thunks in a way that diverges from those that use lazy. E.g.,

     utop # let c = lazy (print_endline "Evaluating!"; 2);;
     val c : int lazy_t = <lazy>

     utop # force c;;
     Evaluating!
     - : int = 2

     utop # force c;;
     - : int = 2

Note that the second time we force c, the result is not re-evaluated. Consequently, the side effect does not occur. This can end up producing totally different results when dealing with impure programs:

     utop #
     let lazy_incr : int ref -> unit Lazy.t =
         fun r -> lazy (incr r)

     let thunk_incr : int ref -> unit -> unit =
       fun r -> fun () -> incr r

     let ref_a = ref 1
     let ref_b = ref 1

     let lazy_incr_ref_a  = lazy_incr ref_a
     let thunk_incr_ref_b = thunk_incr ref_b
     ;;
     val lazy_incr : int ref -> unit lazy_t = <fun>
     val thunk_incr : int ref -> unit -> unit = <fun>
     val ref_a : int ref = {contents = 1}
     val ref_b : int ref = {contents = 1}
     val lazy_incr_ref_a : unit lazy_t = <lazy>
     val thunk_incr_ref_b : unit -> unit = <fun>

     utop # ref_a;;
     - : int ref = {contents = 1}

     utop # ref_b;;
     - : int ref = {contents = 1}

     utop # force lazy_incr_ref_a; ref_a;;
     - : int ref = {contents = 2}

     utop # thnk_incr_ref_b (); ref_b;;
     - : int ref = {contents = 2}

     utop # force lazy_incr_ref_a; ref_a;;
     - : int ref = {contents = 2}

     utop # thunk_incr_ref_b (); ref_b;;
     - : int ref = {contents = 3}

     utop # force lazy_incr_ref_a; ref_a;;
     - : int ref = {contents = 2}

     utop # thunk_incr_ref_b (); ref_b;;
     - : int ref = {contents = 4}

Note that the lazy version only increments the ref cell once, returning the memoized result on each subsequent call. However, the thunk will increment the ref cell every time it is called.

How to make…

a deferred value

     utop # let deferred_value = lazy (print_endline "Now evaluating!"; 3);;
     val deferred_value : int lazy_t = <lazy>

     utop # deferred_value;;
     - : int lazy_t = <lazy>

     utop # force deferred_value;;
     Now evaluating!
     - : int = 3

     utop # force deferred_value;;
     - : int = 3 (* retrieves the memoized result *)

a type that includes deferred evaluations

     type 'a deferred = T of 'a Lazy.t
     let deffered_value : int deferred = T (lazy 3)

a lazy list

     type 'a stream = Cons of 'a * 'a stream Lazy.t

     let cons : 'a -> 'a stream -> 'a stream =
       fun x s -> Cons (x, lazy s)
     let head : 'a stream -> 'a =
       fun (Cons (x, _)) -> x
     let tail : 'a stream -> 'a stream =
       fun (Cons (_, s)) -> Lazy.force s
     let rec take : int -> 'a stream -> 'a list =
       fun n s ->
         if n < 1
         then []
         else head s :: take (pred n) (tail s)