arc> (obj a 1 b 2)
#hash((a . 1) (b . 2))
(let list list:list
(obj a 1 b 2))
#hash((((a 1 . nil) . nil) . ((b 2 . nil) . nil)))
Currently, (obj a 1 b 2) expands into (listtab (list (list 'a 1) (list 'b 2))), which means it uses the local meanings of 'listtab and 'list during evaluation.* The Arc community already removes much of the need for hygiene by following the convention of using (uniq) for local variable names generated by macros, but that doesn't help in this case.
Technically, we could fix this as a community too, as long as we adopt a convention like one of these:
- Have (obj a 1 b 2) expand into ('#<procedure: listtab> ('#<procedure: list> ('#<procedure: list> 'a 1) ('#<procedure: list> 'b 2))), for instance.
- Have (obj a 1 b 2) expand into (eval!listtab (eval!list (eval!list 'a 1) (eval!list 'b 2))), for instance, and avoid using "eval" as a local variable name.
- Avoid using existing global variable names as local variable names, and vice versa.
Whenever possible, I like being able to repair a REPL session just by fixing one buggy global binding, so I don't recommend the first option here. The third option is closest to what we have in practice, but I don't like the way the global/local distinction is hazy without introducing some kind of boilerplate naming convention. So the second option is kind of my favorite, but it still smacks of namespace qualification, and it doesn't help Arc code that already exists.
Semi-Arc takes a "none of the above" approach and just implements the hygiene, despite the incompatibilities that risks. I've only found one positive use of "free symbol capture" in practice,* so that probably won't be the top issue when porting code to Semi-Arc.
* IIRC, an example I wrote had one macro that bound a gensym-named local variable and another macro that referred to it using the same gensym. The second macro referred to the variable plainly enough that it was no more useful than an anaphoric variable, and it could just as well be replaced with a dynamic variable or a global variable. Nevertheless, it may turn out to be a useful technique for a variable that needs to be captured by lexical closures but only needs to be used for a specific purpose (incrementing it and broadcasting an update event, maybe).
I thought about using the same pattern in the Jarc sql package to dynamically turn tracing of the SQL statements on and off. But it seemed inelegant making all the sql functions into macros just to get the dynamic tracing, so I decided to use thread-local variables instead in that case.