Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing map of functions to a macro

Tags:

clojure

I have a macro that will implement a Java interface that is a listener. I defined the macro to take a map containing functions that I want to destructure, and use for each of the interfaces methods. This is the macro :-

(defmacro with-cache-listener-m [component event body]
   (let [{:keys [f-insert f-update]} body]
     `(. ~component addMapListener
     (proxy [AbstractMapListener] []
       (entryInserted [~event] ~f-insert ~event)
       (entryUpdated [~event] ~f-update ~event)))))

The body map is this :-

(def m-callbacks {:f-insert callback-insert :f-update callback-update})

But when I call (macroexpand '(with-cache-listener-m test-cache e m-callbacks)) it expands to (. test-cache user/addMapListener (clojure.core/proxy [com.tangosol.util.AbstractMapListener] [] (user/entryInserted [e] nil e) (user/entryUpdated [e] nil e)))

The callback functions are nil. Do I need to define them differently or am I going about this the wrong way.

like image 752
JPT Avatar asked Apr 28 '11 02:04

JPT


1 Answers

When you call the with-cache-listener-m macro, the body argument get bounded to 'm-callbacks as a symbol, so when you try to destructure that local var it won't work because it ain't a map. You can let the resulting form do the job like this:

(defmacro with-cache-listener-m [component event body]
  `(let [{:keys [f-insert# f-update#]} ~body]
     (. ~component addMapListener
        (proxy [AbstractMapListener] []
          (entryInserted [~event] f-insert# ~event)
          (entryUpdated [~event] f-update# ~event)))))

But in the end I'm not sure your code need a macro, have you tried to write it as a function:

(defn add-map-listener [component insert-fn update-fn]
  (.addMapListener component
    (proxy [AbstractMapListener] []
      (entryInserted [e] (insert-fn e))
      (entryUpdated [e] (update-fn e)))))

As you saw, I changed a couple of things:

  • Made the function name clearer, your macro wasn't really like other with-* macros that usually evaluate some code (the body) in some kind of special context.
  • Removed the event argument as it didn't seemed to have any use.
  • Made the insert-fn and update-fn arguments explicit to simplify the example.
  • Used the new method calling syntax.
  • Fixed the proxy's methods to actually use the given functions.

If you want to make the functions completely optional and make it possible to be given in any order you could always do that:

(defn add-map-listener [component & functions]
  (let [{:keys [insert-fn update-fn]} (into {} functions)]
    (when-not (empty? functions)
      (.addMapListener component 
        (proxy [AbstractMapListener] []
          (entryInserted [e] (insert-fn e))
          (entryUpdated [e] (update-fn e)))))))

Notice that I've added code to not call addMapListener when no functions are given.

like image 80
Nicolas Buduroi Avatar answered Sep 28 '22 19:09

Nicolas Buduroi