I think it is really, really bad to have to type "(do x)" instead of "x" just to avoid macroexpansion. Additionally, I think it is unnecessary: you'd only have to do that because arc3.1 happens not to check for lexical variables when determining whether to macroexpand. This is different from the way it works in Scheme and Common Lisp, and I don't think it was a deliberate design decision, and it is really easy to change:
So just treat it as a bug in arc3.1--it probably won't even cause problems most of the time, because it's kind of a rare case--and fix it in your own ac.scm, and assume that it will be fixed in future users' Arc implementations. Please do not establish "(do x)" as good coding style. If the compiler screwed up whenever you had a variable that had three vowels in a row, the solution would be to fix the damn compiler, not to awkwardly twist up your own code; and if other people were using the old broken compiler, you'd first tell them to upgrade, and only change your own program to cater to the bad compiler if it was absolutely necessary for some reason--and you'd do so after you'd gotten your program the way you wanted it.
Edit: It is probably a hard problem to diagnose if you just leave it there and someone uses it and is like "why is this screwing up?". So if you intend for others to use it, you could put in a thing like this:
(mac achtung (x) `(+ ,x 2))
(let achtung [+ _ 5]
(unless (is (achtung 0) 5)
(err "Oh god you have a bad Arc compiler. Fix that crap:
> So just treat it as a bug in arc3.1--it probably won't even cause problems most of the time, because it's kind of a rare case--and fix it in your own ac.scm, and assume that it will be fixed in future users' Arc implementations.
I'm all too happy to do that. ^_^ Note that it'll probably mean I've introduced bugs to Rainbow and Jarc. :-p
Part of the reason I've harped on the state of affairs that causes me to write (do x), as well as the reason the state of affairs isn't altogether bad (that it makes macros that generate macro forms a tiny bit less susceptible to variable capture), has been in the hope that people will become confident that it's really annoying. ^^;
I honestly didn't know I'd be the only one actively defending it (rather than merely leaving things the way they are or upvoting), so I continued to give it the benefit of the doubt. Let's not do that. :)
> it probably won't even cause problems most of the time, because it's kind of a rare case
Actually, I use local variable names that clash with macros all the time, and that's why I started my (do x) and (call x) patterns in the first place. :) If I remove the cruft right away, my code will almost certainly break until everyone has upgraded. Dunno if anyone but me is going to suffer from my (published) code breaking, but hey. :-p
By the way, in this case, I was programming to a different core language implementation--in fact hacking on something core to that implementation--and I admit I cargo culted.
I was hoping to actually try running some code last night, but no dice. Anyway, that sounds like it would in fact be a problem. In fact, in Arc 3.1 at least, 'defset things are looked up at compile time, and 'get is given special treatment. Try something like this:
(= foo (table) bar (table) baz (table) qux (table)
quux (list nil nil))
(mac get1 (x) `(,x 1))
(let ref bar (= (ref foo) 2))
(let get idfn (= ((get baz) qux) 3))
(let cadr car (= (cadr quux) 4))
I wouldn't worry about 'get too much. It's inconsistent in just the same way as metafns are, so it's just one more in a list of names we shouldn't overwrite or use for local variables.
The setforms case is a bit more troublesome. Maybe they shouldn't be macro-like, and should instead be special-case extentions of 'sref? If the return value of 'get were its own type with 'defset and 'defcall support, that case could be eliminated too.
This may be a case where I've painted myself into a corner with wart. Since wart is built atop a lisp-2, I can't just expand the ssyntax a.b to (a b). I have to generate (call a b). Since I want to be able to say things like ++.a, I need call to handle macros as well. But now (call f x) will try to call a macro called f before it looks for the lexical binding.
It's not as big a problem as it may seem. You don't have to worry about future macros shadowing lexical bindings as long as they load afterwards.
I spent an embarrassingly long time trying to have lexical bindings override macros, before realizing that's impossible in wart: macros get expanded long before lexical bindings are created. So this is a case where you really need a full interpreter; any macro you write can't inspect lexical bindings up the call stack. (oh, for python's nested namespaces..)
I ended up dividing up ac.scm into 17 files, and arc.arc into 26 (the boundary is fuzzy). So far each file seems highly coherent, and most files are short, so the codebase feels the way the old-timers described forth code: "A component can usually be written in one or two screens of Forth." (http://prdownloads.sourceforge.net/thinking-forth/thinking-f..., pg 41; screens are Forth's units of code.)