Search code examples
mongodbconcurrencyclojuremonger

Lazy evaluation with compare-and-set


I'm trying to implement a get-database function that retrieves the database reference from Monger the first time it is called, remembers the value in an atom and returns it directly on subsequent calls. My current code looks like this:

(def database (atom nil))

(defn get-database
  []
  (compare-and-set! database nil
    (let [db (:db (mg/connect-via-uri (System/getenv "MONGOLAB_URI")))] db))
  @database)

The problem is that the let clause seems to be evaluated even if compare-and-set! returns false (i.e. database is not nil). Is there some way to get this to evaluate lazily so I don't incur the penalty of retrieving the Monger connection, or is this approach fundamentally misguided?


Solution

  • The problem here is that compare-and-set! is a function, so evaluating it will evaluate all the parameters before the function is called.

    The typical approach that I take for the use case of caching and re-using some expensive-to-compute value is with a delay:

    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. See also - realized?

    In your case:

    (def database (delay (:db (mg/connect-via-uri (System/getenv "MONGOLAB_URI")))))
    

    Now you can just say @database any time you want to get a reference to the database, and the connection will get initialized the first time your code actually causes the delay to be dereferenced. You could wrap the call to dereference the delay inside a get-database function if you'd like, but this isn't necessary.