The single instance thing is my other con, the one I called singleton.
The place oriented issue is where your state is located and how to find it. To be very concrete, it’s literally that your functions and their code have the reference to the state hard-coded in them.
(def state (atom {:money 0}))
(defn add-money [amount]
(swap! state update-in [:money] #(+ amount %)))
Now, add-money cannot be tested on its own. You need to setup the global state var for it. You can’t reuse add-money in other contexts, because it is coupled to the one where there exist a global state var. And if you ever want to change the place of the global, you break a lot of code, every function that was referencing it directly in a hard-coded way will be broken. Changing its place can be like renaming it, or moving it to a different namespace, putting it behind a database, etc. Literally the place where you store it, store the data, the state in this case. Your state is now stored in a global. If you moved it to a MySQL table, the above code would be broken, because it was depending on the exact place where the state used to be stored, in this case a global var.
Now IoC (inversion of control) would dictate that what is normally in the control of the callee be inverted, so that it is now in control by the caller. One common way to do that is DI (dependency injection).
In my example, finding the state is under the control of add-money
. It does so using a hard coded reference to a global var. We want to invert this, so that the caller, the function that will call add-money
is instead in control of where to find the state.
Here we will do it using DI, we will inject the state map into the add-money
fn to invert the control.
(defn add-money [state-map amount]
(update-in state-map [:money] #(+ amount %)))
That simple! We inverted the control of finding the state map from the callee to the caller, by injecting the state map into the callee.
Now, there’s more things you can invert. For example, add-money
is still in control of extracting money from the state map, and updating it with the new value. You could invert that as well, still using DI:
(defn add-money [money amount]
(+ amount money))
There’s not a whole lot of other ways to do IoC, other than with DI. But there are still some. People say IoC is the principle, and DI is a mechanism to implement the principle.
Here’s another way to apply IoC. In this case, we’re going to use hooks, or what is often called plugins.
(declare ^:dynamic get-state-fn)
(defn add-money [amount]
(update-in (get-state-fn) [:money] #(+ amount %)))
What is happening here is that add-money
gives the caller a hook for it to plugin the “finding the state map” logic into. Thus the caller is now in control, but it doesn’t inject dependencies to do so, instead, it just implements the get-state-fn hook to create a plugin that add-money
knows how to use. Like so:
(binding [get-state-fn (constantly {:money 10})]
(add-money 300))
I hope this makes things a lot clearer for you.
If you want to read more about it, I’ll shamelessly recommend an old blog post of mine: Rubber Ducking: Inversion of Control [IoC] Vs. Dependency Injection [DI] which also includes links to even more reading.