This is not a 'problem', this is what dynamic means.
> To quote Rich once more, "The overhead for capturing the dynamic
> context
> for every lazy seq op would be extreme, and would effectively render
> dynamics non-dynamic."
>
> To help alleviate the problem somewhat, a (bound-fn ..) helper macro
> has
> been created (https://www.assembla.com/spaces/clojure/tickets/170)
> but my guess is that its use would be impractical/ugly/risky..
> it would need to be used "all over the place" and forgetting to use it
> in any of those places could introduce a bug.
>
I don't think so. There are people who are sending off jobs to agents
that they know will used the dynamic environment for which bound-fn
will work perfectly. And most code need never consider it. If you need
it all over the place you have too much use of dynamic vars and
laziness + side-effects. The person who needs to think about this is
the person using send/future etc with context-sensitive work. If there
were to be generic capturing points, it might be macros wrapping
those.
> I've been thinking about an alternative to (bound-fn ..) and would
> like your opinions on the following tweak to Clojure:
>
> ...Implementation details elided...
> With a better integrated, better designed implementation, I'm
> certain this could be improved further.
> In that case, would this be a worthwhile enhancement to Clojure?
> Seems like it could be a win-win situation, since it rescues
> (semi-)dynamic bindings from the gnashing jaws of laziness
> for those that want to use it, but shouldn't impact negatively
> upon those that don't?
> Or is there something fundamentally wrong with the idea?
>
Before leaping to implementation/performance issues, I think it is
important to think about the semantics of this - what does it mean? I
think you will get a lot of confusion, given:
(defn foo []
(send-off-something-that-uses-env (fn [] ... (use-env))))
(defn bar []
(establish-env env
(foo)))
If fns 'capture' these environments when created, and re-establish
them when called, then foo itself will have captured the environment
at *its* definition/creation point, and will re-establish that, thus
the environment setup by bar will not be conveyed through foo to
something-that-uses-env - *but*, if you substituted the body of foo
for its call, it would. That's bad.
Rich
Hi Laurent,
I think the responsibility should be placed with the one creating
On Sep 30, 9:46 am, Laurent PETIT <laurent.pe...@gmail.com> wrote:
> Where should the responsability be placed ? Should the user of the library,
> in doubt, place everywhere in his code bind-fn calls to protect it ? Should
> the library author use bind-fn before dispatching to other threads (with the
> problem that the library author may not know which dynamic vars are
> relevant) ...
another thread. There are several scenarios:
* in the library:
* if only library code is involved the author knows (hopefully)
whether the dynamic environment must be saved or not
* if a user callback is involved, require it to be pure (ie.
depending only on the arguments) or
* use bound-fn to be safe if non-pure functions are allowed.
* in the user code:
* the user of the library should now, when library functions are non-
pure and hence bound-fn is necessary for a new thread.
Dynamic Vars have a high correlation with side-effects. So making such
things sufficiently ugly (but not too ugly) helps to make you aware of
side-effects and keep them apart of the (hopefully existing)
functional core. There are already examples where only non-side-
effecting functions are allowed: in transactions, as validators, ...
Does this make sense?
It simply doesn't compose or flow. Making the nil env special (e.g.
non-replacing) just moves the problem into higher-order functions that
use the construct:
(defn needs-x []
(use-env-x))
(defn needs-y []
(use-env-y))
(defn foo []
(with-env x (fn [f] (needs-x) (f))))
(let [f (foo)]
(with-env y (f needs-y)))
needs-y isn't going to get it.
Rich
Am 01.10.2009 um 19:08 schrieb Rich Hickey:
> It simply doesn't compose or flow. Making the nil env special (e.g.
> non-replacing) just moves the problem into higher-order functions that
> use the construct:
>
> (defn needs-x []
> (use-env-x))
>
> (defn needs-y []
> (use-env-y))
>
> (defn foo []
> (with-env x (fn [f] (needs-x) (f))))
>
> (let [f (foo)]
> (with-env y (f needs-y)))
>
> needs-y isn't going to get it.
This is "simply" solved by having a chain of environments. The
algorithm works like this:
* The desired value is contained in the current map. => Use it.
("Younger" bindings override "older" ones)
* The desired value is not contained go up one step in the chain of
env maps and repeat.
* If there is no further step in the chain (ie. we arrived at nil),
use the root binding of the Var.
"simply" with quotes, because I have no clue about the performance
impact of that. It should fix your example, though.
Elk, an rather old, embeddable Scheme interpreter, constructs its
environment like that.
Sincerely
Meikel
Yes, I know how linked environments work, and in Clojure you wouldn't
do that but would instead just assoc onto the incoming env map. Then
at least lookups will remain fast. But environment extension will
still be an overhead, and more important will persist even if not
needed.
E.g. it still has scope issues, since when someone says (with-env x
...) they have an expectation of the dynamic extent of the binding
ending with the block, and it won't necessarily if any
closure/laziness escapes.
(defn bar []
(fn [] (doesnt-use-big-thing)))
(defn needs-a-big-thing []
(use-a-big-thing)
(bar))
(defn foo []
(with-env a-big-thing
(needs-a-big-thing)))
;a-big-thing is kept around needlessly
Overall these requests just seem to be - I wish dynamic binding knew
what I needed and did it (only when I need it to), without any real
semantics.
Note that I am interested in a construct with useful semantics, but we
need to get some semantics before implementation details.
Rich