Search code examples
clojure

delay and defonce both give similar results


I am trying to understand the difference between delay and defonce, and one should be used over the other.

Below is a snippet from tomekw/hikari-cp

(defonce datasource
  (delay (hcp/make-datasource datasource-options)))

Why is defonce and delay both used ? Is it not enough to just use defonce ?

As I understand both will allow the execution of the form just once. What is the difference between both ?


Solution

  • in the notion of times the body is being evaluated, both of them do call the body exactly once, but this form is not solely about the execution times, it is also about the lazyness/eagerness. delay macro wraps the body into the special reference object, and postpones it's execution until the first time it is dereferenced:

    user> (let [x (delay 100)]
            (println x)
            (Thread/sleep 100)
            (println "woke up")
            (println x)
            (println @x)
            (println x)
            @x)
    
    ;;=> #delay[{:status :pending, :val nil} 0x273336d4]
    ;;   woke up
    ;;   #delay[{:status :pending, :val nil} 0x273336d4]
    ;;   100
    ;;   #delay[{:status :ready, :val 100} 0x273336d4]
    ;;   100
    

    defonce itself is eager, returning the delayed block at once, but not executing it.

    so that means that datasource is being set to encapsulated lazy block, which should be executed exactly when the code first needs to get the data source (calling deref or @), and defonce is here to prohibit redefinition of this var. I would call it 'lazy singleton'.

    user> (defonce value
            (do (println "DEFINING!")
                (delay
                  (println "EVALUATING!")
                  101)))
    ;;=> DEFINING!
    #'user/value
    
    user> value
    #<Delay@b1388c1a: :not-delivered>
    
    user> @value
    ;;=> EVALUATING!
    101
    
    user> value
    #<Delay@1ed9ec81: 101>
    
    ;; `delay` caches value (doesn't print again)
    user> @value
    101