The intern
function in Clojure is a powerful construct. This is similar to MOP in Groovy. It really comes in handy in many situations. I particularly use it in unit tests to mock or override implementation of data layer methods and to monkey patch third party libraries dynamically.
; com.external.core.clj
(ns com.external.core)
(defn tangle
"An internal method which takes an argument but always return the tangle string."
[msg]
"tangle")
Now let us say that this is a third party library that we included from maven (or clojars) as project dependency. And we found an issue, the issue being it should return "untangle" instead of "tangle" but say we cannot upgrading the library to the latest version which has the fix. This is where intern
comes in handy.
; com.example.init.clj
(ns com.example.init)
(defn untangle
"Function that fixes a bug in an function that lives in an external namespace."
[x]
"untangle")
;; Update the binding, means all callers will see the new patched function, even
;; callers in other any libraries.
(intern 'com.external.core 'tangle untangle)
What happens here is that the intern
updates the binding of function tangle
in the namespace com.external.core
to the untangle
function defined in com.example.init
namespace. From now on any call to the original function will use the updated patched function instead. This is very neat in certain situations where we need to monkey patch. This is not function override, because this change is global as in the entire app sees the new definition. Note that the function signature must be the same. Else it will break the callers.
This is also helpful in unit testing Clojure code. Say we have a service layer and calls into data layer. But we do not want to hit the DB or API methods, as we are not really performing integration tests. So we can provide mock methods inside the deftest
with mock results, which will easily help in testing our service layer code.
There is a caveat. This does not work well with Clojure macros, as macros are compile time which generates code. So at runtime, there really is no macro code binding that we can override.