Passing map of functions to a macro
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 w开发者_JAVA技巧ant 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.
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.
Macros are not functions: they only know about the literal forms passed to them at compile-time. If you assign a value, say 10, to the var x
, then pass x
to your macro, it sees not 10 but x
. Your macro will probably work fine if, instead of def
ing m-callbacks
and then passing that symbol, you simply pass the map directly as a literal.
精彩评论