Search code examples
clojure

Extend swap! behaviour for nil type in Clojure


let suppose, I have var atom-var

(def atom-val (atom []))

Also suppose a standard behaviour of atom:

(swap! atom-val conj {:b "2"})
=> #object[clojure.lang.Atom 0x6500d3fd {:status :ready, :val [{:a "2"}]
(@atom-val)
=> #object[clojure.lang.Atom 0x6500d3fd {:status :ready, :val [{:a "2"}]

I want to create the same behaviour as it would work with nil object, but without actions:

(def atom-val nil)
(swap! atom-val conj "New val")

Of course, I will get a NullPointerException. But I want that nothing happened, supress it. I do not need to write try every time it, I just need the described behavuiour.

I see that swap! is a function, atom is a function, atom returns clojure.lang.IAtom, clojure.lang.IAtom is an interface. I cannot extend interface. How can I get the described behaviour?

Well, I have a global dynamic variable which is equal to nil

(def ^:dynamic  atom-val nil). 

Whenever a thread is created (it's ring handler with compojure), I am binding atom-val to

(defn func [handler] 
   (fn [request]
     (binding [atom-val (atom [])]
        (handler request)
    )
 )

So, I have such a form in different functions:

(swap! atom-val conj "New val"). 

I can run it everywhere lots of times (inside/outside different functions). It's really bad to check every time whether atom-val is null or not. Functions have to make swap!, but sometimes atom-val cannot be initialized properly (when a function makes swap! outside ring handlers, before binding).

So I decided to do it this way: I'd like to extend swap! protocol for Atom and when nil (when atom-val is nil) is passed it mustn't throw NullPointerException.


Solution

  • If you want an atom that does nothing, you can write:

    (def noop-atom
      (reify clojure.lang.IAtom
        (reset [_ _])
        (swap [_ _])
        (swap [_ _ _])
        (swap [_ _ _ _])
        (swap [_ _ _ _ _])
        (compareAndSet [_ _ _])))
    

    You can then use this atom as the root-value of the dynamic var.

    If your goal is to manage state during the lifecycle of a Ring request/response, you can write a custom middleware.