Clojure: memfn
Join the DZone community and get the full member experience.
Join For FreeThe other day I stumbled upon Clojure's memfn macro.
The memfn macro expands into code that creates a fn that expects to be passed an object and any args and calls the named instance method on the object passing the args. Use when you want to treat a Java method as a first-class fn.(map (memfn charAt i) ["fred" "ethel" "lucy"] [1 2 3]) -> (\r \h \y)-- clojure.org
At first glance it appeared to be something nice, but even the documentation states that "...it is almost always preferable to do this directly now..." - with an anonymous function.
I pondered memfn. If it's almost always preferable to use an anonymous function, when is it preferable to use memfn? Nothing came to mind, so I moved on and never really gave memfn another thought.(map #(.charAt %1 %2) ["fred" "ethel" "lucy"] [1 2 3]) -> (\r \h \y)-- clojure.org, again
Then the day came where I needed to test some Clojure code that called some very ugly and complex Java.
In
production we have an object that is created in Java and passed
directly to Clojure. Interacting with this object is easy (in
production); however, creating an instance of that class (while testing)
is an entirely different task. My interaction with the instance is
minimal, only one method call, but it's an important method call. It
needs to work perfectly today and every day forward.
I
tried to construct the object myself. I wanted to test my interaction
with this object from Clojure, but creating an instance turned out to be
quite a significant task. After failing to easily create an instance
after 15 minutes I decided to see if memfn could provide a solution. I'd
never actually used memfn, but the documentation seemed promising.
In
order to verify the behavior I was looking for, all I'll I needed was a
function that I could rebind to return an expected value. The memfn
macro provided exactly what I needed.
As a (contrived) example, let's assume you want to create a new order with a sequence id generated by incrementAndGet on AtomicLong. In production you'll use an actual AtomicLong and you might see something like the example below.
(def sequence-generator (AtomicLong.))While that might be exactly what you need in production, it's generally preferable to use something more explicit while testing. I haven't found an easy way to rebind a Java method (.incrementAndGet in our example); however, if I use memfn I can create a first-class function that is easily rebound.
(defn new-order []
(hash-map :id (.incrementAndGet sequence-generator)))
(println (new-order)) ; => {:id 1}
(println (new-order)) ; => {:id 2}
(def sequence-generator (AtomicLong.)) (def inc&get (memfn incrementAndGet)) (defn new-order [] (hash-map :id (inc&get sequence-generator))) (println (new-order)) ; => {:id 1} (println (new-order)) ; => {:id 2}At this point we can see that memfn is calling our AtomicLong and our results haven't been altered in anyway. The final example shows a version that uses binding to ensure that inc&get always returns 10.
(def sequence-generator (AtomicLong.)) (def inc&get (memfn incrementAndGet)) (defn new-order [] (hash-map :id (inc&get sequence-generator))) (println (new-order)) ; => 1 (println (new-order)) ; => 2 (binding [inc&get (fn [_] 10)] (println (new-order)) ; => 10 (println (new-order))) ; => 10With inc&get being constant, we can now easily test our new-order function.
Clojure
Opinions expressed by DZone contributors are their own.
Comments