Clojure

bound-fn macro

Details

  • Type: Defect Defect
  • Status: Closed Closed
  • Resolution: Completed
  • Affects Version/s: None
  • Fix Version/s: Release 1.1
  • Component/s: None
  • Labels:
    None
  • Approval:
    Ok

Description

Added bound-fn macro to define a function, which saves away the current thread-local bindings. Before executing the body these bindings are restored. Useful for helper functions running on a different thread but requiring the same thread-local bindings.

For discussion see:
http://groups.google.com/group/clojure/browse_frm/thread/69367d3981de81d1

Needs patch from ticket:
https://www.assembla.com/spaces/clojure/tickets/169-thread-local-bindings-interface

Currently two fns are created. One wrapping the other into the necessary code. I also added the binding* helper function, because I missed it several times in my projects. So it may also be useful to others.

Activity

Hide
Assembla Importer added a comment -
Show
Assembla Importer added a comment - Converted from http://www.assembla.com/spaces/clojure/tickets/170 Attachments: 0002-Added-bound-fn-to-define-thread-local-binding-aware.patch - https://www.assembla.com/spaces/clojure/documents/btVJwSHgyr3QpHeJe5afGb/download/btVJwSHgyr3QpHeJe5afGb
Hide
Assembla Importer added a comment -

meikelbrandmeyer said: [file:btVJwSHgyr3QpHeJe5afGb]: Patch adding bound-fn helper macro

Show
Assembla Importer added a comment - meikelbrandmeyer said: [file:btVJwSHgyr3QpHeJe5afGb]: Patch adding bound-fn helper macro
Hide
Assembla Importer added a comment -

meikelbrandmeyer said: Another implementation I considered is this:

(defmacro bound-fn
  [args & body]
  (let [fn-name (when (symbol? args) args)
        args    (if fn-name (first body) args)
        body    (if fn-name (next body) body)]
    `(let [bindings# (get-thread-bindings)]
       (fn ~@(if fn-name
               (list fn-name args)
               (list args))
         (push-thread-bindings bindings#)
         (try
           ~@body
           (finally
             (pop-thread-bindings)))))))
</code></pre>

This would allow for lazy-seq functions:

<pre><code>
(defn some-seq
  [x]
  (let [step (bound-fn step [s]
               (lazy-seq
                 (when-let [s (seq s)]
                   (cons (first s) (step (rest s))))))]
    (step x)))

I found this second solution quite ugly, because I have to know how a fntail looks like and fiddle around with. But when Jon Harrop mentioned the lazy-seq in the discussion on the list, I noticed that the current solution (as in the patch at the moment), does not allow such recursion.

Show
Assembla Importer added a comment - meikelbrandmeyer said: Another implementation I considered is this:
(defmacro bound-fn
  [args & body]
  (let [fn-name (when (symbol? args) args)
        args    (if fn-name (first body) args)
        body    (if fn-name (next body) body)]
    `(let [bindings# (get-thread-bindings)]
       (fn ~@(if fn-name
               (list fn-name args)
               (list args))
         (push-thread-bindings bindings#)
         (try
           ~@body
           (finally
             (pop-thread-bindings)))))))
</code></pre>

This would allow for lazy-seq functions:

<pre><code>
(defn some-seq
  [x]
  (let [step (bound-fn step [s]
               (lazy-seq
                 (when-let [s (seq s)]
                   (cons (first s) (step (rest s))))))]
    (step x)))
I found this second solution quite ugly, because I have to know how a fntail looks like and fiddle around with. But when Jon Harrop mentioned the lazy-seq in the discussion on the list, I noticed that the current solution (as in the patch at the moment), does not allow such recursion.
Hide
Assembla Importer added a comment -

meikelbrandmeyer said: After some feedback from Laurent, we arrived with the new attached patch. The seq example might then look like this:

(defn my-hypothetical-seq
  [coll]
  (let [bindings (get-thread-bindings)
        step     (fn step [s]
                   (lazy-seq
                     (with-bindings bindings
                       (generate-seq-here-calling-step))))]
    (step coll)))

This is should allow for such a use without to many hassles.

Show
Assembla Importer added a comment - meikelbrandmeyer said: After some feedback from Laurent, we arrived with the new attached patch. The seq example might then look like this:
(defn my-hypothetical-seq
  [coll]
  (let [bindings (get-thread-bindings)
        step     (fn step [s]
                   (lazy-seq
                     (with-bindings bindings
                       (generate-seq-here-calling-step))))]
    (step coll)))
This is should allow for such a use without to many hassles.
Hide
Assembla Importer added a comment -

meikelbrandmeyer said: Hmmm.. Would it be worthwile to also allow an optional map to specify a subset of the bindings? Currently, all bindings are stored in the map, while maybe only a few are actually needed. Would this be pre-mature optimisation?

Show
Assembla Importer added a comment - meikelbrandmeyer said: Hmmm.. Would it be worthwile to also allow an optional map to specify a subset of the bindings? Currently, all bindings are stored in the map, while maybe only a few are actually needed. Would this be pre-mature optimisation?
Hide
Assembla Importer added a comment -

chouser@n01se.net said: I think this API is solid. In particular, I think having with-bindings means that bound-fn can stay simple – if someone wants to tweak the map of Vars, they can use get-thread-bindings, select-keys and with-bindings with an appropriate amount of hassle (ie, not much).

I wonder if the macros would be better implemented with code repeated from the * functions. As it is, only a little repetition in core is avoided, at the cost of more fn classes, more fn objects, and extra entries in stack traces.

Show
Assembla Importer added a comment - chouser@n01se.net said: I think this API is solid. In particular, I think having with-bindings means that bound-fn can stay simple – if someone wants to tweak the map of Vars, they can use get-thread-bindings, select-keys and with-bindings with an appropriate amount of hassle (ie, not much). I wonder if the macros would be better implemented with code repeated from the * functions. As it is, only a little repetition in core is avoided, at the cost of more fn classes, more fn objects, and extra entries in stack traces.
Hide
Assembla Importer added a comment -

meikelbrandmeyer said: I'm not sure, that it is only repetition which is saved. bound-fn would be more elaborate (can't just take a fntail) or more restricted (if only takes a body).

However what does "restricted" mean here? What's the use of allowing to name a bound-fn (ie. something like {{(bound-fn this [x y z] ....))}} in face of missing TCO? Should we hope for soon TCO support and take care for it already?

But even without TCO considerations: what about {{(bound-fn ([x] ...) ([x y] ...))}}? Supporting such forms (we have to go into each branch setting up the bindings or we get another wrapping fn, which doesn't save much to the original proposal...) would make the bound-fn macro really more complicated. And here naming would again make sense to allow for default invocations dispatch to another arity. Would this be used often enough for it to be worthwhile to support the full fn syntax?

If yes to the above, that leaves only with-bindings as a candidate for repetitive implementation. Since the macro is only small, the code repetition imposed by a fatter macro is still smaller than the generated class file for a thunk. So maybe voting for repetition in this case is a valid point.

Should I modify the patch?

Show
Assembla Importer added a comment - meikelbrandmeyer said: I'm not sure, that it is only repetition which is saved. bound-fn would be more elaborate (can't just take a fntail) or more restricted (if only takes a body). However what does "restricted" mean here? What's the use of allowing to name a bound-fn (ie. something like {{(bound-fn this [x y z] ....))}} in face of missing TCO? Should we hope for soon TCO support and take care for it already? But even without TCO considerations: what about {{(bound-fn ([x] ...) ([x y] ...))}}? Supporting such forms (we have to go into each branch setting up the bindings or we get another wrapping fn, which doesn't save much to the original proposal...) would make the bound-fn macro really more complicated. And here naming would again make sense to allow for default invocations dispatch to another arity. Would this be used often enough for it to be worthwhile to support the full fn syntax? If yes to the above, that leaves only with-bindings as a candidate for repetitive implementation. Since the macro is only small, the code repetition imposed by a fatter macro is still smaller than the generated class file for a thunk. So maybe voting for repetition in this case is a valid point. Should I modify the patch?
Hide
Assembla Importer added a comment -

meikelbrandmeyer said: (In [[r:fbacc4a5751fa5c15baa599b5a058cd81b05a247]]) Added bound-fn to define thread-local binding aware functions

bound-fn captures the thread-local bindings in effect where the
function is defined. Installs these bindings before executing the
body. This is useful for helper functions running in a different
thread.

Excluded with-bindings from clojure/main.clj to prevent name clash.

Fixes #170

Signed-off-by: Chouser <chouser@n01se.net>

Branch: master

Show
Assembla Importer added a comment - meikelbrandmeyer said: (In [[r:fbacc4a5751fa5c15baa599b5a058cd81b05a247]]) Added bound-fn to define thread-local binding aware functions bound-fn captures the thread-local bindings in effect where the function is defined. Installs these bindings before executing the body. This is useful for helper functions running in a different thread. Excluded with-bindings from clojure/main.clj to prevent name clash. Fixes #170 Signed-off-by: Chouser <chouser@n01se.net> Branch: master

People

Vote (0)
Watch (0)

Dates

  • Created:
    Updated:
    Resolved: