Hi Andrew, Am 31.03.2009 um 04:53 schrieb Andrew Stein:
Here's a set of macros I have found useful for creating simulated thread-local bindings for specific agents:
Please allow me to give you some feedback concerning the style of your macros. - (list 'let ....), (vector 'ba# a), .... This is most likely not what you want. 'foo will expand exactly to the symbol foo, ie. it will capture the binding of foo in the context where the macro is expanded. This is bad and should generally be avoided. There are exceptions, eg. this in proxy, but they should only be used rarely and their use should clearly documented. `foo on the other hand will expand to the foo known to your macro and hence always work as expected without clashing with the code that actually uses the macro. - 'ba# is a good idea to generate a variable in the macro expansionwhich is almost surely not used by the code calling the macro. However
this only works with `ba#. 'ba# is just the symbol ba#, which is not protected against capture. Additionally the # notation only works inside a ` form. So when you write (list `ba# `ba#) the second will be different from the first. - This leads to the general form. Instead of building the data-structures manually simply use syntax-quote. Compare the two forms: (list `foo x) vs. `(foo ~x). I find the second much clearer. It allows to read the macro almost like the final expansion. - The #^clojure.lang.Agent tag is actually not correct. The parameter to the macro is not an agent. It is a form! Maybe just a symbol (nameing a Var at runtime) or a form like (agent 1), that is a list consisting of the symbol "agent" and the number "1". The macro is expanded at compile time, while the agent is only created at runtime. So the macro actually never sees the agent itself. Don't forget: the macro is a function acting on code! Not on runtime values! (Disclaimer: Henceforth everything is untested.)
(defmacro bind-agent "Adds bindings to an agent's metadata" [#^clojure.lang.Agent a bindings] (list 'let (vector 'ba# a) (list 'do (list 'alter-meta! 'ba# 'assoc :agent-bindings (list 'quote bindings)) 'ba#)))
I would write this as follows. (Note, I'm not sure how you exactly use your code, but I would capture the vars immediately.) (defmacro bind-agent [a bindings] (let [bindings (mapcat (fn [[the-var the-val]] [`(var ~the-var) the-val]) (partition 2 bindings))] `(let [bound-agent# a] (alter-meta! bound-agent# assoc :agent-bindings (hash-map ~...@bindings)))))
(defmacro send-bound "Send to an agent with metadata bindings" [#^clojure.lang.Agent a fun] (list 'send a (list 'fn ['& 'x#] (list 'binding (-> a qualify-sym find-var var-get meta :agent-bindings) (list 'apply fun 'x#)))))
Now it gets a bit ugly. In your version as well as in mine. In yours we have to jump through hoops to retrieve the Var where the agent is stored. But the limits the usefulness to such agents. Agents stored in locals, refs or atoms fall under the table. In my version we have to re-invent binding. I really miss binding* which acts like binding but is a function and takes a map and a thunk. (defn send-bound [a f & args] (if-let [bindings (:agent-bindings (meta a))] (send a #(try (clojure.lang.Var/pushThreadBindings bindings) (apply f % args) (finally (clojure.lang.Var/popThreadBindings))))) (apply send a f args)) Please note, what we got from resolving the vars immediately when a "bound" agent is created: - send-bound is not a macro anymore. (<- That's a Good Thing) - send-bound now works on any agent. Not only agents stored in Vars. - The #^clojure.lang.Agent tag would now make sense, allthough it's not necessary. Phew. Long answer. I hope it helps, though. Sincerely Meikel
smime.p7s
Description: S/MIME cryptographic signature