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?
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
orderef
/@
), and will cache the result and return it on all subsequentforce
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)
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".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With