Lazy Data Structures
- OCaml is by default eager
- function parameters are evaluated to values before calling functions
- Pairs, records, and variants all have their internals computed to values recursively.
- But, sometimes laziness can be both useful and more efficient
- for lazy funcation call, no need to compute arguments that are not used
- It allows for construction of “infinite” lists, etc
- Don’t compute the nth element until it is asked for
- But, once it is computed, cache it (a form of memoizing)
- Just don’t ask for all infinitely many elements!
Super simple encoding of laziness in OCaml
- OCaml has no built-in Laziness (Haskell does)
- But it can be encoded via a thunk
let frozen_add = fun () -> printf "Have a smiley day!\n"; 4 + 3
let thaw e = e ()
thaw frozen_add;; (* 4+3 not computed until here *)
- This simple encoding is in fact just “call by name”, laziness means memoizing the result.
The Core.Lazy
module
- ` Core.Lazy` is a much more usable sugar for the above
# open Lazy;;
# let l = lazy(printf "Have a smiley day!\n";2+3);;
val l : int lazy_t = <lazy> (* lazy_t is the wrapper type *)
# force l;;
Have a smiley day!
- : int = 5
# let f lv = (force lv) + (force lv);;
val f : int lazy_t -> int = <fun>
# f l;;
Have a smiley day! (* this is printed only once, the 2nd force uses cached 5 value *)
- : int = 10
open Core
open Lazy
type 'a stream = Cons of 'a * 'a stream Lazy.t (* A stream is an infinite list - no empty list case here *)
(* Programs making lazy lists look somewhat bizarre at first - doesn't this loop forever?!? *)
let rec all_ones : int stream = Cons(1,lazy(all_ones))
let rec ints n : int stream = Cons(n,lazy(ints (n+1)))
(* Code to get the nth element of a lazy list *)
let rec nth (Cons(hd, tl) : 'a stream) (n : int) :'a =
if n = 0 then hd
else nth (force tl) (n-1)
(* A more interesting example - shows memoization, this is not exponential *)
let rec fib : int stream =
let rec fib_rest (Cons(hd, tl) : int stream) : (int stream) =
let Cons(hd',_) = force tl in (* Note can't pattern match on first-2 together due to force needed *)
Cons (hd + hd', lazy(fib_rest (force tl))) in
Cons(1, lazy(Cons(1, lazy(fib_rest fib))))
nth fib 100;; (* clearly not exponential *)
- As we saw above with the print,
lazy
results are cached so if it is forced a second time no need to recompute- Once the list is “unrolled” by one call it doesn’t need to be “re-unrolled”
- This is a form of caching / memoization built into
Lazy
- Becuase of that the above
nth fib
function will in fact be linear, not exponential