4 ms·
Having a macro in one module refer to bindings it has no idea about what they are seems nasty to me. I'd rather have hygiene and break it where I chose to. That
by bjoli 8y ago
Having a macro in one module refer to bindings it has no idea about what they are seems nasty to me. I'd rather have hygiene and break it where I chose to. That seems less error prone.
- kazinator 8y agoThat's just the thing. A macro calculates a datum. That datum doesn't refer; it's just a datum. (defmacro foo() '(a b c)) a, b and c do not refer here; they are in a quote. Of course, more usually we use a backquote, and insert interesting things into the template. The non-inserted template parts are data, just like in a quote. We don't know whether b and c will ever refer. a will refer to some operator or function at the expansion site, which determines the meaning of b and c. In any case, none of them refer to anything while they are still inside the macro. Hygienic macros implement a scheme whereby the quote is removed, and, wee, the datum does refer to the surrounding environment even though it's still supposedly a datum that will be inserted elsewhere where it becomes code. Although this has a touted benefit (the hygiene), it has a nasty implementation. Just think about what it takes to have a (list ...) expression in a macro template still refer to the global list function that is in scope at the macro definition, even though the expansion site has defined a local list. Somehow we have to "wormhole" around the shadowing definition to reach that list. Now let's think about it in the context of separately compiled modules. How about the requirement for a macroexpand function for debugging macros: will the output be understandable? There is an obvious down-side. While we have made the macro expansion immune to local shadowing definitions, the macro is now susceptible to its own. If we redefine list around the macro, it's referring to the wrong one. A non-hygienic macro doesn't have this problem: (defmacro foo () (labels ((list () ...))) `(list ...,@(list ...)))) Here, when the macro is used, the generated code will likely refer to the correct list library function, not to the one in the macro. The unquote-spliced expression ,@(list ...) uses the local one. It's all clear. We know what is in the quote and what isn't.
- bjoli 8y agoI understand what you mean, but I am not sure I agree. As a macro writer i either want to depend on the local definition explicitly (using datum->syntax in syntax case or not renaming in an er-macro-transformer). I also don't want to accidentally alter behaviour of macros if I happen to shadow a binding. I have made a version of racket's for loops that depends on expansion to other macros (to be able to split the complexity in smaller chunks). Having that expand unhygienically would open the macro up to subtle errors. I also don't have to re-export bindings that I happen to use (like a specialised let from srfi-71 that supports multiple return values) and not changing let for whoever imports my library or polluting their namespace. Expanding the macro in guile makes it clear in the generated code where things come from a (let ...) in the generated code gets expanded to ((@@ (loops for-loops) let) ...), Which is easily debuggable. I just don't happen to see what you are describing about hygiene as a problem. It is the behaviour I want as a macro writer and the vast majority of time as a macro user. The only big problems with it are 1. The Implementation sucks. The concepts are quite easy to grook, but hard to implement. 2. The hygiene has some edge cases where it might not protect you if you are writing very complex macros gluing quoted syntax objects together.