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 expansion
which 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

Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to