Note: This is not an officially supported Google product.
Lazy
is a Carp module that provides support for lazy computations. The main
entry point exposed by the module is the Lazy.lazy
macro which takes a body of
some computation and returns a corresponding lazy computation. Here's an example
of Lazy
in action:
(use Lazy)
(defn add-one [x] (+ x 1))
;; Traditional, strict evaluation. The following function application is
;; evaluated immediately.
(add-one 2)
=> 3
;; A lazy variant of the prior computation.
(defn lazy-add-one [x] (Lazy.lazy (+ x 1)))
;; The original computation is wrapped in a thunk, which defers the evaluation
;; of the underlying function application.
(lazy-add-one)
=> ƛ
;; Force a lazy computation to compute by making an additional call.
((lazy-add-one 1))
=> 2
The Lazy
module also exposes a submodule called Typed
. The Lazy.Typed
module is functionally equivalent, except that it wraps all lazy computations in
a Lazy
type. Computations wrapped in this type must be evaluated using an
explicit call to force
.
While the untyped variant of the module is more flexible, the additional wrapper
type exposed by Lazy.Typed
communicates more to callers--you can tell
precisely where lazy computations are expected or used by looking at a
function's signature.
(use Lazy.Typed)
(defn lazy-add-one [x] (Lazy.Typed.lazy (+ x 1)))
;; Typed lazy computations return a member of the `Lazy` type, which contains
;; the underlying computation.
(lazy-add-one 2)
=> (Lazy ƛ)
;; Use the `force` function to compute a typed lazy computation.
;; Note that force expects a reference to a Lazy type value.
(Lazy.Typed.force &(lazy-add-one 2))
Lazy computations support memoization--any lazy computation that's bound to a lexical environment won't be recomputed after it's computed for the first time--instead, the return value is stored and returned immediately upon subsequent invocations of the lazy computation.
(defn add-one [x] (+ x 1))
;; This call isn't memoized, since two distinct lazy computations are
;; instantiated, even though their underlying computation is equivalent.
(+ ((Lazy.lazy (add-one 2))) ((Lazy.lazy (add-one 2))))
=> Computed Value (3)
=> Computed Value (3)
=> 6
;; Contrarily, bound computations are memoized:
(def lazy-add-one (Lazy.lazy (add-one 2)))
(+ (lazy-add-one) (lazy-add-one))
;; The call to lazy-add-one is only computed once. The second application of the
;; lazy computation reuses the computed value.
=> Computed Value (3)
=> 6
It's often convenient to use ad-hoc memoization to store the result of a partial computation. You can use let bindings to bind and memoize lazy computations in function bodies:
(defn lazy-plus-plus [x y]
(let [m (Lazy.lazy (+ x y))]
(+ (m) (m))))
(lazy-plus-plus 2 2)
=> Computed Value (4)
=> 8
This library does not play well with the Carp REPL. You'll find that a lot of its functionality, such as memoization, doesn't work as expected when definitions are called directly in a REPL environment.