Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do lazy variables exist in Clojure?

I have a bit of computation that is somewhat expensive (starting a database), and I only want to create the database if I actually am going to use it. I am looking for a reference variable (or just a plain variable, if that is possible) that would only evaluate its value in the event that it is used (or dereferenced). Something conceptually like the following.

(def v (lazy-var (fn [] (do (println "REALLY EXPENSIVE FUNCTION") true))))

and in the future, when I either just use var v, or call @v, I then get it to print out "REALLY EXPENSIVE FUNCTION", and from thereon v has a value of true. The important thing here is that the fn was not evaluated until the variable was (de)referenced. When needed, the function is evaluated once and only once to calculate the value of the variable. Is this possible in clojure?

like image 866
Stephen Cagle Avatar asked Jun 14 '12 03:06

Stephen Cagle


2 Answers

delay would be perfect for this application:

delay- (delay & body)

Takes a body of expressions and yields a Delay object that will invoke the body only the first time it is forced (with force or deref/@), and will cache the result and return it on all subsequent force calls.

Place the code to construct the database handle within the body of a delay invocation, stored as a Var. Then dereference this Var whenever you need to use the DB handle — on the first dereference the body will be run, and on subsequent dereferences the cached handle will be returned.

(def db (delay (println "DB stuff") x))

(select @db ...) ; "DB stuff" printed, x returned
(insert @db ...) ; x returned (cached)
like image 161
Jon Gauthier Avatar answered Oct 27 '22 21:10

Jon Gauthier


Clojure 1.3 introduced memoize function for this purpose:

(memoize f)

Returns a memoized version of a referentially transparent function. The memoized version of the function keeps a cache of the mapping from arguments to results and, when calls with the same arguments are repeated often, has higher performance at the expense of higher memory use.

In your example replace non-existing lazy-var with memoize:

(def v (memoize (fn [] (do (println "REALLY EXPENSIVE FUNCTION") true))))
(v)
=>REALLY EXPENSIVE FUNCTION
=>true
(v)
=>true

(delay expr) also does the job as another answer explains. An extra comment on dereferencing the delay - the difference between force and deref/@ is that force does not throw exception if used on non-delay variable while deref/@ may throw ClassCastException "cannot be cast to clojure.lang.IDeref".

like image 43
Grzegorz Luczywo Avatar answered Oct 27 '22 19:10

Grzegorz Luczywo