That is not possible as written, because the "defmacro" is not executed to define the macro until after the outer let is already executing, which is after macro expansion of that form (and thereby its children), as it has already been read: so what I get for that is a really weird error that I'm passing too many arguments to "foo", as if it were a function (which it is not; albeit I'm not certain what it is ;P).
However, I can use the def-inline that I wrote in the edit to my earlier reply to demonstrate that if you reorganized this code in a way that was semantically equivalent but hoisted the macro, it would work the way you think it should: the definition of the thing from the let surrounding the macro-ish definition is used, not the one from the call site (or the global one in the namespace).
(edit:) Oh, that wasn't semantically equivalent, as the second let is not inside of the first. However, if I do that, I get the same behavior as I get in the other case (that it doesn't actually expand the macro at all and treats the form as a function call), as I'm obviously just defining the macro again inside of the same already-read form in which I'm using it.
(further:) Okay, and the reason why that is working is that the way I wrote def-inline inlined the code from def-inline into the macro itself. That is probably not what you wanted from def-inline: this is more like def-const (or def-static or something). I thereby tried doing this instead:
This, in fact, does not return the "correct" string: instead, it fails to work at all, as the "thing" used inside of the macro is supposedly not defined (and worse, if I have a global def for "thing", I get that value). So, this is is a case where the macro is unable to use bindings that are local to the time when the macro definition is executed: it can only deal with global-ish names.
For the record, I think that is unrelated to what I normally think of with relation to hygiene: the macro is able to modify the code using it without accidental capture, but humorously it, itself, is unable to take advantage of symbols that have been bound locally around it. I totally accept that this is probably a flaw (I only say "probably" as I'm willing to believe someone from Clojure can convince me otherwise; it certainly seems like a flaw, though).
(more:) I am increasing the weight of that "probably", as I'm noting that the person calling this code has absolutely no way to refer to the thing that I have access to: there is no path no matter how complex or awesome that would let it refer to my "middle thing". I can inline it with ~, but then it isn't a binding anymore; however, that's actually equivalent, as Clojure does not have setf: "thing" is a constant, and so even if I had a function inside of this let closed over that thing, I couldn't modify its value.
I'd forgotten about the whole Lisp tradition of expanding + evaluating one form at a time -- once again betraying my Scheme biases. :) That's making it hard for me to think about how to compare the two. I'm really not familiar enough with the one-form-at-a-time approach.
Anyway, my takeaways here are:
- I shouldn't've said anything about Clojure specifically, because a) the design space is different and b) they have some form of hygiene-like something-or-other that I don't know enough about. Gotta go study them!
- I still don't know how to do hygiene in Scheme-like languages any other way than the approaches I've seen, but I do agree they're more complicated than I wish they were.