Clojure Scoping Rules

223 views
Skip to first unread message

kunjaan

unread,
Nov 20, 2009, 10:40:34 PM11/20/09
to Clojure
Even though I have used Clojure, I hadn't looked at the scoping rules
in detail. I am getting more confused as I read the documentations. I
made a small test to try out the scoping resolutions and am apalled at
the complexity. Could somebody explain the intent and various rules
that Clojure uses?

(def x 1)

(defn dummy-fn2[]
(+ x 1))

(defn dummy-fn[]
(println "entering function: " x)
(let [x 100]
(println "after let: " x)
(let [x (dummy-fn2)]
(println "after let and dummy2: " x)
(binding [x 100]
(println "after binding: " x)
(let [x (dummy-fn2)]
(println "after binding and dummy2: " x))))))

1:2 foo=> (dummy-fn)
entering function: 1
after let: 100
after let and dummy2: 2
after binding: 2
after binding and dummy2: 101
nil

Mark Engelberg

unread,
Nov 20, 2009, 11:02:35 PM11/20/09
to clo...@googlegroups.com
Conceptually, the best way to think about it is that your binding sets
the global x to 100 and restores the global x to 1 when exiting the
scope of the binding construct. It has no effect on your local x.

Dynamic binding is confusing, and should be used with care. If you
stick with the lexical scoping of function closures and lets, things
should behave the way you expect.

--Mark
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

Mark Engelberg

unread,
Nov 20, 2009, 11:22:31 PM11/20/09
to clo...@googlegroups.com
On Fri, Nov 20, 2009 at 8:02 PM, Mark Engelberg
<mark.en...@gmail.com> wrote:
> Dynamic binding is confusing, and should be used with care.

Elaborating on my previous point: it's especially confusing because
dynamic binding interacts poorly with lazy data structures, which is
predominantly what you deal with in Clojure. If your lazy data
structure depends on something that is dynamically bound using
binding, there is a good chance it won't get evaluated until after you
exit the scope of the binding and the global variable is restored to
its earlier state, so your lazy data structure ends up not using the
value that was set with binding. It can get messy fast.

There were a couple times I used dynamic binding, and I was really
excited about it. I had never used a language with a dynamic binding
construct, and it seemed like a perfect fit for what I was doing. I
was threading a couple of values through a bunch of mutually recursive
functions that generated a tree. The values I was threading
controlled certain aspects of the tree generation. I realized that by
making these generation parameters global values, and just wrapping
binding to set the parameters around the call to my tree generator, I
could avoid all these extra inputs on a whole slew of functions that
were just passing these parameters around so they'd be available in
the functions that really needed them. But my tree generator used
laziness in some non-obvious places, and it took me a long time to
notice that when I didn't fully evaluate the tree right away, I was
getting very different results. It was hard to track down the
problem, and ultimately I had to go back to threading all the
parameters through the functions explicitly. The experience really
turned me off of dynamic binding in Clojure, since it has such a
strong laziness focus.

Which reminds me, every once in a while I see people talking about
this here, and brainstorming up some alternatives to binding that
might interact better with lazy data structures. Has there been any
real progress on this, or has every proposed solution been equally
problematic?

Armando Blancas

unread,
Nov 21, 2009, 11:12:42 AM11/21/09
to Clojure
I sympathize with your difficulties, but isn't there something
fundamentally incompatible between the later-or-never of lazy-seq and
the this-way-here-and-now for which dynamic binding is good for? In
this case you picked laziness over code simplification, maybe it'll be
the other way around some times. I'm not sure if both features belong
together.

Mark Engelberg

unread,
Nov 21, 2009, 12:17:53 PM11/21/09
to clo...@googlegroups.com
The point is that many built-in Clojure functions return lazy
sequences. If you want to choose code simplification over laziness,
it's not always even clear how to do that safely. For example, it's
not uncommon to end up with lazy sequences of lazy sequences, for
example, and then a single doall isn't going to the trick. Wrapping
doall around every single call in your code is unrealistic.

So yes, I agree that these features don't coexist well together, but I
also contend that it's extremely difficult to eliminate all uses of
laziness from complex code. Therefore, binding is a construct that
should almost always be avoided, IMO.

Intuitively, it seems to me that what one wants is for lazy data
structures to contain bound values in some sort of closure-like thing
so rebindings after the fact don't affect the data structure, but
regular functions should retain their fully-dynamic behavior. I
haven't really thought it through, so probably this doesn't quite
work, but this kind of separation between the behavior of data
structures and functions when dealing with dynamic binding is what's
been going through my mind.

Graham Fawcett

unread,
Nov 21, 2009, 1:42:41 PM11/21/09
to clo...@googlegroups.com
On Sat, Nov 21, 2009 at 12:17 PM, Mark Engelberg
<mark.en...@gmail.com> wrote:
> Intuitively, it seems to me that what one wants is for lazy data
> structures to contain bound values in some sort of closure-like thing
> so rebindings after the fact don't affect the data structure, but
> regular functions should retain their fully-dynamic behavior.  I
> haven't really thought it through, so probably this doesn't quite
> work, but this kind of separation between the behavior of data
> structures and functions when dealing with dynamic binding is what's
> been going through my mind.

In Haskell, you might use a state-carrying monad as the 'closure-like
thing'. If you squint enough, those monads look an awful lot like
dynamic binding contexts. They have the variable-hiding properties of
dynamic binding, but with a scope that's more amenable to lazy
evaluation.

The same technique ought to work in Clojure. I haven't played at all
with the monad tools in clojure.contrib, but you might want to give
them a try.

If you're new to monads, there are quite a few Haskell tutorials; I
won't try to enumerate them. ;-) This link is an overview of the
Reader monad, which I think is most similar to the dynamic binding
construct:

http://www.haskell.org/all_about_monads/html/readermonad.html

Best,
Graham

nchubrich

unread,
Nov 21, 2009, 9:32:45 PM11/21/09
to Clojure
Regarding Clojure sequence functions: why couldn't they have the
option of returning non-lazy seqs? Because you don't always really
need lazy seqs. It might be nice to have the option.

(map (fn[x] x) [1 2 3] :strict) -> [1 2 3]

or even

(binding [*strict* true]
(map (fn[x] x) [1 2 3]))

-> [1 2 3]

Richard Newman

unread,
Nov 21, 2009, 9:35:24 PM11/21/09
to clo...@googlegroups.com
> It might be nice to have the option.

You do -- wrap the form in doall.

> (binding [*strict* true]
> (map (fn[x] x) [1 2 3]))

would be equivalent to

(doall (map identity [1 2 3]))

ataggart

unread,
Nov 21, 2009, 10:16:39 PM11/21/09
to Clojure
Getting back to the initial post, this would be the (almost)
equivalent code in java:

public class Test{

static int x = 1;

void bindingX(int val){
x = val;
}

int dummyFn2(){
return x + 1;
}

void dummyFn(){
System.out.println("entering function: " + x);
int x = 100;
System.out.println("after let: " + x);
x = dummyFn2();
System.out.println("after let and dummy2: " + x);
bindingX(100);
System.out.println("after binding: " + x);
x = dummyFn2();
System.out.println("after binding and dummy2: " + x);
}

public static void main(String[] args){
new Test().dummyFn();
}
}


The confusion lies in poor naming style, conflating local names with
global names, and not writing functions in a functional style.

cody koeninger

unread,
Nov 22, 2009, 12:31:53 AM11/22/09
to Clojure
http://clojure.org/lisps

"All (global) Vars can be dynamically rebound without interfering with
lexical local bindings. No special declarations are necessary to
distinguish between dynamic and lexical bindings."

Other part of that explanation is whether x in a given piece of code
refers to a lexical (local) or dynamic (global) value.
It seems that if there's a local value available for x, it always
hides the global value for x.
Global value is only looked up when there's no local value available
in the surrounding text.

binding may look like a lexical construct, since it has braces that
enclose an area of code, but it's not.
It affects code that actually executes during that time, not code that
was written in that space.

And yeah, dynamic scope interacting with laziness is the single most
confusing thing about Clojure, even if you've had a little lisp
experience.
Use the variants of def just for functions or macros; use ref or let
for variables; avoid using binding.

nchubrich

unread,
Nov 22, 2009, 2:32:15 PM11/22/09
to Clojure
Richard---
It's not the same thing:

(class (doall (map (fn [x] x) [1 2 3])))
-> clojure.lang.LazySeq

whereas

(class (binding [*strict* true]
(map (fn[x] x) [1 2 3])))
-> clojure.lang.LazilyPersistentVector

Also, having a dynamic var that turns laziness on and off would allow
you to do it once for any given scope, without having to add the extra
'ceremony' of doalls. To quote Mark Engleberg:

"If you want to choose code simplification over laziness,
it's not always even clear how to do that safely. For example, it's
not uncommon to end up with lazy sequences of lazy sequences [...],
and then a single doall isn't going to the trick. Wrapping
doall around every single call in your code is unrealistic. "

I would add to that that casting your types back to what they were is
also unrealistic.

If there were a *strict* dynamic var, then you \could choose code
simplification over laziness with a single line. What would be wrong
with that?

Richard Newman

unread,
Nov 22, 2009, 3:10:24 PM11/22/09
to clo...@googlegroups.com
> Richard---
> It's not the same thing:
>
> (class (doall (map (fn [x] x) [1 2 3])))
> -> clojure.lang.LazySeq
>
> whereas
>
> (class (binding [*strict* true]
> (map (fn[x] x) [1 2 3])))
> -> clojure.lang.LazilyPersistentVector

From Clojure's perspective those *are* the same thing:

user=> (= (map identity [1 2 3]) [1 2 3])
true

Also, just because it's lazy doesn't mean it hasn't already done the
work, and

user=> (def *x* 1)
#'user/*x*
user=> (doseq [y (map (fn [x] (* *x* x)) [1 2 3])]
(println y))
1
2
3
nil
user=> (doseq [y (binding [*x* 2]
(map (fn [x] (* *x* x)) [1 2 3]))]
(println y))
1
2
3
nil
user=> (doseq [y (binding [*x* 2]
(doall
(map (fn [x] (* *x* x)) [1 2 3])))]
(println y))
2
4
6
nil

The result of doall is a sequence that has already been evaluated.

If you want a concrete sequence, you can get one: simply call seq on
the output, or use vec or into.

user=> (type (binding [*x* 2] (doall (map (fn [x] (* *x* x)) [1 2 3]))))
clojure.lang.LazySeq

user=> (type (seq (binding [*x* 2] (doall (map (fn [x] (* *x* x)) [1 2
3])))))
clojure.lang.ChunkedCons

user=> (type (vec (binding [*x* 2] (doall (map (fn [x] (* *x* x)) [1 2
3])))))
clojure.lang.PersistentVector

user=> (vec (binding [*x* 2] (doall (map (fn [x] (* *x* x)) [1 2 3]))))
[2 4 6]


> Also, having a dynamic var that turns laziness on and off would allow
> you to do it once for any given scope, without having to add the extra
> 'ceremony' of doalls.

Sure, but it would either require complicating the core, or making
functions that use lazy-seq pay attention to the binding of that var.
Not fun -- it would introduce a problem that library authors have to
think about.


> I would add to that that casting your types back to what they were is
> also unrealistic.

I get the impression that programming with *abstractions* is the
Clojure way. Dependence on particular sequence types is usually bad.
If you need to, though, it's only a vec call away. If you call vec
inside a binding form, you don't even need to realize the lazy
sequence yourself.


> If there were a *strict* dynamic var, then you \could choose code
> simplification over laziness with a single line. What would be wrong
> with that?

To play devil's advocate: it complicates the implementation (and the
implementation of libraries); the strictness would be undesirably
viral (what if you accidentally cause a library to realize an infinite
lazy sequence?); and for all I know it would screw up JIT optimization.

My opinion is that an easier way to realize nested lazy sequences
would be a more elegant solution to your problem; doall*, say. This
could almost be implemented as

(defmulti doall* class)

(defmethod doall* clojure.lang.LazySeq [x]
(seq (doall (map doall* x))))

(defmethod doall* :default [x] x)

...

;; Make some nested LazySeqs.
user=> (type (map (fn [x] (repeat x :foo)) [1 2 3]))
clojure.lang.LazySeq

user=> (map type (map (fn [x] (repeat x :foo)) [1 2 3]))
(clojure.lang.LazySeq clojure.lang.LazySeq clojure.lang.LazySeq)

;; Recursively eager evaluation.
user=> (type (doall* (map (fn [x] (repeat x :foo)) [1 2 3])))
clojure.lang.ChunkedCons

user=> (map type (doall* (map (fn [x] (repeat x :foo)) [1 2 3])))
(clojure.lang.Cons clojure.lang.Cons clojure.lang.Cons)


You can omit the seq call in doall* if all you want is bindings
capture/eager evaluation, and don't actually mind that it's LazySeq
implementing ISeq rather than a 'concrete' sequence.

nchubrich

unread,
Nov 22, 2009, 4:26:01 PM11/22/09
to Clojure
Richard---

> What if you accidentally cause a library to realize an infinite
> lazy sequence?

True, that's a problem. But couldn't the library protect itself by
putting a (binding [*strict* false] ...) in front of its code?
(Having a namespace-level binding construct would be helpful.)

This raises a more general thought about binding in general (while
we're talking about new language constructs). Right now you have
basically two choices for influencing code: Parameter passing, which
is, "nothing happens to my code without my permission", and dynamic
binding, which is "You might have the rug yanked from under you, and
there's nothing you can do about it!" (Exclamation point here is an
indicator of mutation, not exclamation.)

Well, this might be wildly speculative, but couldn't there be a middle
way? I.E. a scope or a function body (if we are trying to unify these
concepts then Scope and Function Body is really the same thing),
instead of exporting variables of things that can be substituted,
basically sets up a negotiation over what can and should be bound. It
might state things like "binding this such-and-such a way is a \really
\bad idea, but if you do X first, it would be OK; and of course if you
\insist...." The actual binding (if such a guard has been put up)
then doesn't happen until the conditions have been run through.

I'm not really sure what this construct would look like. Somebody
else might have a better idea....

Maybe the problem with binding is not that it's inherently evil, it's
just not democratic enough.

Richard Newman

unread,
Nov 22, 2009, 5:18:35 PM11/22/09
to clo...@googlegroups.com
> True, that's a problem. But couldn't the library protect itself by
> putting a (binding [*strict* false] ...) in front of its code?
> (Having a namespace-level binding construct would be helpful.)

That's exactly what I meant when I wrote "it would introduce a problem
that library authors have to think about".

I don't support any solution that requires libraries to "protect
themselves" from decisions made in user code, particularly when it's
so easy to externally control the evaluation of lazy sequences -- just
visit them within the correct bindings!

> This raises a more general thought about binding in general (while
> we're talking about new language constructs). Right now you have
> basically two choices for influencing code: Parameter passing, which
> is, "nothing happens to my code without my permission", and dynamic
> binding, which is "You might have the rug yanked from under you, and
> there's nothing you can do about it!" (Exclamation point here is an
> indicator of mutation, not exclamation.)

An interesting point that I don't think anyone's addressed: it's not
so much that the rug might be yanked from under you, but that you
might find yourself on a different rug when asked to do the work. That
can actually be advantageous; you can create lazy sequences and hand
them somewhere else, where the appropriate bindings will be enforced
during evaluation. (Maybe selecting an output stream or queue
dynamically while processing a lazy stream of values, for example).

Perhaps I'm just an optimist, or think a little differently.

> I'm not really sure what this construct would look like. Somebody
> else might have a better idea....

I believe that Rich has done some speculative work on scoping
constructs.

> Maybe the problem with binding is not that it's inherently evil, it's
> just not democratic enough.

Heh, I would say it's too democratic — there's something you want to
do, but the broader 'society' (the existing configuration of lazy
constructs and bindings) wants to do something else, so you must suffer!

I'd venture a different opinion: most programmers are not used to
thinking of evaluation outside the lexical order (much the same as the
switch to concurrent programming).

Once you get that, it ceases to be a big deal, and you use appropriate
techniques (thunks, controlling evaluation yourself, taking care to
choose your scopes, etc.) without much conscious thought. This switch
causes a divide in discussions, where one side sees a huge problem to
be fixed, and the other side sees an interesting language feature
which both requires care and offers power, but arises naturally from
the semantics of the language.

nchubrich

unread,
Nov 22, 2009, 8:33:34 PM11/22/09
to Clojure
Richard, do you know where one can read about Rich Hickey's
speculative work on scoping constructs? I did find a good description
by him of what Clojure currently does, from 2007:

http://markmail.org/message/kpuq4dvcavek26sp#query:+page:1+mid:mgfsubipgaqdmzru+state:results

Richard Newman

unread,
Nov 22, 2009, 8:40:53 PM11/22/09
to clo...@googlegroups.com
> Richard, do you know where one can read about Rich Hickey's
> speculative work on scoping constructs? I did find a good description
> by him of what Clojure currently does, from 2007:

http://clojure.org/todo

http://www.assembla.com/spaces/clojure/tickets/2-Scopes

Better off asking Rich, I think :) I believe "Scopes" as written is
related to things like with-open more than bindings, but I'm sure
there's interplay.

Meikel Brandmeyer

unread,
Nov 21, 2009, 4:37:03 PM11/21/09
to clo...@googlegroups.com
Hi,

Am 21.11.2009 um 05:22 schrieb Mark Engelberg:

> Which reminds me, every once in a while I see people talking about
> this here, and brainstorming up some alternatives to binding that
> might interact better with lazy data structures. Has there been any
> real progress on this, or has every proposed solution been equally
> problematic?

I wrote up a little blog post on the problem and the possible
solutions. Feedback welcome.

http://kotka.de/blog/clojure/Taming_the_Bound_Seq.html

Sincerely
Meikel

nchubrich

unread,
Nov 23, 2009, 9:32:41 AM11/23/09
to Clojure
Meikel, is get-thread-bindings only in a development version of
Clojure? I have 1.09.11 and don't see it documented or usable (or in
the online docs).

Chouser

unread,
Nov 23, 2009, 9:36:50 AM11/23/09
to clo...@googlegroups.com
That's excellent Meikel, thanks. Any reson you didn't use the
with-bindings macro to make your final example a bit simpler?

--Chouser

Meikel Brandmeyer

unread,
Nov 23, 2009, 10:10:57 AM11/23/09
to Clojure
Hi,

On Nov 23, 3:36 pm, Chouser <chou...@gmail.com> wrote:

> >http://kotka.de/blog/clojure/Taming_the_Bound_Seq.html
>
> That's excellent Meikel, thanks.  Any reson you didn't use the
> with-bindings macro to make your final example a bit simpler?

Woops. The reason might be the time of day (23 o'clock after getting
up on 5:30 in the morning). I simply forgot about with-bindings. :( I
will update the post tonight. Maybe I'll also show a more detailed
example for bound-fn.

Sincerely
Meikel

Meikel Brandmeyer

unread,
Nov 23, 2009, 10:20:42 AM11/23/09
to Clojure
Hi,
I'm not sure what 1.09.11 means, but the following commits added push-/
pop-/get-thread-bindings as wells as with-bindings and bound-fn.

thread-bindings interface: http://github.com/richhickey/clojure/commit/110b9c2eb8a128d837e6e620efc7e1c4e33feb82
with-bindings and bound-fn: http://github.com/richhickey/clojure/commit/fbacc4a5751fa5c15baa599b5a058cd81b05a247

Sincerely
Meikel

Graham Fawcett

unread,
Nov 23, 2009, 3:21:47 PM11/23/09
to clo...@googlegroups.com
On Sat, Nov 21, 2009 at 4:37 PM, Meikel Brandmeyer <m...@kotka.de> wrote:
> Hi,
>
> Am 21.11.2009 um 05:22 schrieb Mark Engelberg:
>
>> Which reminds me, every once in a while I see people talking about
>> this here, and brainstorming up some alternatives to binding that
>> might interact better with lazy data structures.  Has there been any
>> real progress on this, or has every proposed solution been equally
>> problematic?
>
> I wrote up a little blog post on the problem and the possible solutions.
> Feedback welcome.

Very nice. A generalized version might be more useful to your readers:
take an input seq, and return an output seq which is evaluated
stepwise in the binding environment.

Best,
Graham

Meikel Brandmeyer

unread,
Nov 23, 2009, 4:46:04 PM11/23/09
to clo...@googlegroups.com
Chris, Graham,

Am 23.11.2009 um 21:21 schrieb Graham Fawcett:

> Very nice. A generalized version might be more useful to your readers:
> take an input seq, and return an output seq which is evaluated
> stepwise in the binding environment.

Thank you for your good comments. I updated the post with a bound-seq
helper, which turns any given lazy input sequence into a bound
sequence. The environment might be chosen manually in the style of
binding. Or alternatively get-thread-bindings captures the whole
environment.

http://kotka.de/blog/clojure/Taming_the_Bound_Seq.html

Sincerely
Meikel

André Ferreira

unread,
Nov 23, 2009, 7:29:25 PM11/23/09
to Clojure
Would it be possible to create an implementation of delay and lazy-seq
that didn't use fn to delay evaluation, or atleast captured dynamic
variables?

(delay (+ x 3)) reasonable semantics in current clojure (let [x x]
(delay (+ x 3)))
(delay (fn [y] (+ x y))) semantics should be the same it already is (x
should get it's value from the dynamic binding)
(delay [x (fn [y] (+ x y))]) semantics should be (let [G_N x] (delay
[G_N (fn [y] (+ x y))])), so all captured variables would need
renaming using gensyms.

delay would need it's body macroexpanded code to be able to correctly
capture variables. It would also try to capture local variables, but
that wouldn't change the semantics and would probably be optimized
away by the compiler.
I might have missed some important detail, since dynamic binding and
lazyness have such a peculiar semantics interaction, but it would
yield much more reasoanable execution, wouldn't it?

Mark Engelberg

unread,
Nov 24, 2009, 1:43:06 AM11/24/09
to clo...@googlegroups.com
Meikel's blog post quotes:
"running into a lot of such trouble is a sign, that you misuse dynamic
variables. Use them wisely."

I'd like to see examples of what you think is a good, clean,
compelling use of dynamic variables that are properly used wisely.

My own experience is that if the code is simple enough for you to
analyze the use of binding and be sure it is correct, then the code is
also simple enough to have easily written in another way (perhaps
using explicit parameter passing). On the other hand, if the use of
binding is complex enough to really matter, it is also sufficiently
complex you can't be 100% sure binding will do what you expect.

I even somewhat question the places where Clojure internally uses
bindings. For example, if you use with-precision to try to control
floating point behavior within a structure that potentially has some
deep laziness (like a tree) that can't easily be forced with a doall,
you're in for a surprise.

I would like to be proven wrong, so I'm serious about wanting to see
good examples of dynamic binding.

Garth Sheldon-Coulson

unread,
Nov 24, 2009, 2:39:51 AM11/24/09
to clo...@googlegroups.com
Hi Mark,

In Clojuratica I make what I think is "good, clean, compelling use" of dynamic vars. I rewrote the code to use dynamic vars after I found that doing it the other way became unwieldy and inelegant.

To simplify a little, the API consists of just one main function, let's call it math-evaluate.

Every time the user calls math-evaluate, he or she can specify any of a number of flags. Just for illustration, let's say two of these flags are :flag1 and :flag2.

The math-evaluate function parses the flags and places them in a map called *options* (or options before I was using dynamic vars). It then calls into the core functions of the code base and returns the result. The core functions call one another in complex ways. There are scores of core functions. All of them have need to have access to the options map, if not for their own consumption then for passing to other core functions.

Without dynamic vars, every single function in my code base would need to have an options argument. Moreover, every mutual call between these scores of functions would need to include the options map as an argument. I did it this way for a while, and it was painful.

Moreover, if in the future I decided that every core function needed access to *two* of these global-within-the-dynamic-scope vars instead of one, I would need to edit the signature of every function and edit every mutual call between the functions.

It turned out that this actually happened: I decided I needed another map to store a few I/O bindings (it's called *kernel*). Because I was using dynamic vars, I was able to change just one location in my code---the namespace where the dynamics are interned---instead of several hundred locations.

The trade-off is the following. When I need to return a function or lazy seq I need to close over the *options* and *kernel* vars by jumping through the hoops Meikel has documented. There are only five or six such places in the code, so I'm more than happy to do this. It feels quite elegant to me, although I'd like a little more syntactic sugar.

Note that the dynamic vars are solely for my convenience as a programmer, not part of the API of the software. I shield them from the user, so if the user tried to bind *options* him- or herself it would have no effect on the code. Every call to math-evaluate binds the dynamic vars anew on the basis of the flags passed. My use of dynamics is an implementation detail.

The source is on github. The "real-laziness" branch is the newest and uses bound lazy-seqs the most, particularly in clojuratica.base.parse.

Comments on this usage welcome.

Garth

Mark Engelberg

unread,
Nov 24, 2009, 10:37:16 AM11/24/09
to clo...@googlegroups.com
On Mon, Nov 23, 2009 at 11:39 PM, Garth Sheldon-Coulson <ga...@mit.edu> wrote:
> Hi Mark,
>
> In Clojuratica I make what I think is "good, clean, compelling use" of
> dynamic vars. I rewrote the code to use dynamic vars after I found that
> doing it the other way became unwieldy and inelegant.

OK this makes sense to me.

Your use case is actually very similar to mine (lots of interrelated
functions where it would be cleaner to have them refer to some sort of
global variable that is set prior to execution). But I got tripped up
because:
1. I think I had more use of laziness in my code than how you describe yours.
2. I didn't know the push-thread-bindings/pop-thread-bindings trick
to fix the spots with laziness (I tried just forcing the lazy
sequences which killed my performance -- I really needed laziness).
3. Even if I had known the trick, I may have had difficulty
identifying all the spots where laziness was an issue since I was
returning a complex lazy tree structure. (I probably would have
tried, though.)

So now that you've explained your use of dynamic bindings, I can
totally imagine scenarios where it is easier to "tame the laziness"
than go back to passing all the "globals" around, especially with the
convenient bound-seq macro that Meikel posted.

This brings up another question in my mind. What kind of impact would
it have on people's code if *all* calls to lazy-seq operated the way
that bound-seq does? Would this make things more intuitive or less
intuitive? Would this radically reduce performance?

Garth Sheldon-Coulson

unread,
Nov 24, 2009, 12:47:12 PM11/24/09
to clo...@googlegroups.com
Happy it helped.

I should mention that I used Meikel's docs as a guide, but my code
don't actually push or pop all the thread bindings every time I return
a lazy seq or a fn.

It felt a little ugly to me to bind *all* the dynamic vars in the
namespace when I knew there were only two I needed to care about. So,
in the locations where I build lazy seqs or fns to return to the user,
I "manually" close over and bind *options* and *kernel*.

I'd be really interested in hearing others' views on the propriety of
binding all the dynamic vars every time using bound-fn or equivalent.
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your
> first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
>

--
Sent from my mobile device

Meikel Brandmeyer

unread,
Nov 24, 2009, 2:24:02 PM11/24/09
to clo...@googlegroups.com
Hi,

Am 24.11.2009 um 18:47 schrieb Garth Sheldon-Coulson:

> I'd be really interested in hearing others' views on the propriety of
> binding all the dynamic vars every time using bound-fn or equivalent.

I asked whether it should to take a map or not in the assembla thread
of the ticket. But there was no response on the question. So I guess
it is ok. It really only considers vars with a non-root binding. So
unless you bind thousand different Vars it's likely not a problem. You
wouldn't use binding inside a tight loop anyway...

Sincerely
Meikel

Reply all
Reply to author
Forward
0 new messages