In a blog post on dependency inversion in Clojure I’ve discussed what this DI principle actually is about and the solutions Clojure offers to support it. There is one aspect that bugged me a little: For me, a fundamental challenge with DI in a language like Clojure is that you often have simple functions depending on simple other functions (simple here in contrast to protocols). In the article linked to above I discussed one way of resolving function dependencies by using an indirection over a service locator. However, in practice writing service locator lookups by hand was getting tiresome soon. So instead I decided to have some fun throwing together some macros that handling function signatures, which resulted in a small library.
funsig shoots lower than Clojure protocols: it provides dependency management on a per-function level. What this means is simply that you can define a function signature with
defsig and then provide implementations with
defimpl. Implementations will depend on the signature. Let’s say we have some application code that depends on a
(ns my.onion) (defn printer [string] (println string)) (defn print-account-multiplied [account multiplier] (let [result (* account multiplier)] (printer result)))
One might want more flexibility on how and where to print. In other words, one might want the application code (
print-account-multiplied) to depend on an abstraction (
printer) only and not on the concrete implementation as in this example.
Funsig allows you to inverse the dependency of the
printer implementation. You would define the signature with
defsig and have the appplication code depend on the signature like this:
(ns my.onion (:require [de.find-method.funsig :as di :refer [defsig defimpl]])) (defsig printer [string]) (defn print-account-multiplied [account multiplier] (let [result (* account multiplier)] (printer result)))
You can then provide the implementation with
(ns my.onion.simle-printer (:require [de.find.method.funsig :as di :refer [defimpl]] [my.onion :as mo :refer [printer]])) (defimpl printer [string] (println string))
Note that the implementation has a dependency on the signature, not the other way around. Also, application code (
print-account-multiplied) simply depends on the signature — here the signature is in the same file, but reference to the var in another namespace (i.e. using
require\:refer) also works normally. For application code, this looks like dependency injection.
funsig will also allow you to have multiple implementations and then select the one you want.