anko / eslisp

un-opinionated S-expression syntax and macro system for JavaScript
ISC License
528 stars 31 forks source link

Macro Hygiene #61

Open VincentToups opened 3 years ago

VincentToups commented 3 years ago

The documentation says lisp-like hygienic macros are supported but there isn't much else by way of documentation. Can you elaborate? Most of the time (Common) Lisp style macros are not called hygienic.

anko commented 3 years ago

You're right, the documentation is imprecise. Worse; it's also incorrect.

I was going by the Wikipedia definition of hygienic macro: Redefining a macro that previously-defined macros are using shouldn't change how the outputs of those previously-defined macros are interpreted. As you say, Common Lisp's macro system does not have this property. (Scheme's does though.)

I recall that eslisp had that property at some point, but it seems I never added a test for it, and have since broken it without noticing:

(! x) ; -> !x;

(macro not (lambda (x) (return `(! ,x))))

(not x) ; -> !x;

(macro ! (lambda (x) (return `(notReally ,x))))

(! x) ; -> notReally(x);

(not x) ; -> notReally(x);
        ; Should still be !x;.  This is not hygienic!

I'll remove the word for now, and look into restoring the feature in the future.

Thanks for the heads-up.

VincentToups commented 3 years ago

Scheme syntax-transformations have a somewhat broader meaning for the word hygiene. I'm hardly an expert in hygienic macros (though I am very familiar with syntax-case) but the key feature in my mind is that symbols introduced during macro expansion are guaranteed to not clash with any other symbols which may be in scope for the code which is being transformed.

My hand-waving way of thinking of this is that symbol names in both the input code and those introduced by the transformer are abstracted away from the relations they represent: two symbols may both be named "x" but they never the less remain distinct symbols and their bindings are not necessarily the same. Another way to think of it is that in order to reference an x in some code added by a macro-expansion you must also enclose the code in a form which provides a binding for that x. Code expanded by the macro may refer to an x but such references remain distinct.

So once you have macro hygiene you almost always want to think about how to violate it. I like syntax-case as a model of how this ought to work but there are other methods.