5 ms·
How are Lisp's macros easier to handle than syntax-rules? On the one hand, you have a function that receives an s-expression, manually deconstructs and transfo
by groovy2shoes 10y ago
How are Lisp's macros easier to handle than syntax-rules? On the one hand, you have a function that receives an s-expression, manually deconstructs and transforms the s-expression while (hopefully) carefully, manually aliasing any symbols to avoid inadvertent capture, and constructs a replacement s-expression. On the other hand, you have a pattern-matching facility that automatically takes care of deconstruction and hygiene for you. Yes, syntax-rules is strictly less powerful than defmacro, but in practice it covers the vast majority of use cases very well.
I'd also like to mention that avoiding variable capture is only one part of macro hygiene. The other part is referential transparency in the face of shadowing. Common Lisp partly provides this because names in the standard package can't be redefined, but it's still wide open when it comes to other names. Consider this (admittedly contrived, for simplicity) example:
(defun square (x)
(* x x))
(defmacro hypotenuse (x y)
`(sqrt (+ (square ,x) (square ,y))))
...
(let* ((square (make-square s))
(diagonal (hypotenuse s s))) ; ERROR! tried to apply
...) ; #'* to a square!
In the expansion of `hypotenuse`, `square` refers to the local variable bound on the line above it, while the macro author clearly intended it to refer to the "global" variable up top. This is a situation that's fairly easy to run into, and horribly confusing when it occurs (especially if the user of the macro didn't write it). Hygiene ensures that the reference to `square` in `hypotenuse` will always refer to the binding of `square` that was in scope at the time the macro was defined, thus avoiding the problem. On its own, `gensym` is necessary but not sufficient for hygiene.
- junke 10y agoReally, an ERROR? Could you show the actual message that was printed? You can't, because you did not even bother to try. Here is an actual test: (defun square (x) (* x x)) (defmacro hypotenuse (x y) `(sqrt (+ (square ,x) (square ,y)))) ;; whatever (defun make-square (s) (list :square s)) (defun foo (s) (let* ((square (make-square s)) (diagonal (hypotenuse s s))) diagonal)) Now, let's compile (SBCL): ; caught STYLE-WARNING: ; The variable SQUARE is defined but never used. Not really what you expected, right? (foo 10) => 14.142136 It looks like it works. You are trying to apply Scheme's semantics to CL. You forgot to take into account CL namespaces, which consist of (1) function/classes/type/variable namespaces and (2) namespaces defined by packages (or custom mappings). > namespace n. 1. bindings whose denotations are restricted to a particular kind. ``The bindings of names to tags is the tag namespace.'' 2. any mapping whose domain is a set of names. ``A package defines a namespace.'' (http://clhs.lisp.se/Body/26_glo_n.htm http://clhs.lisp.se/Body/26_glo_n.htm) If you defined a local function named square, that could have been the beginning of a point. However, I don't buy the idea that inside a single package, someone is going to shadow a function that belongs to the same package, in a macro (if this happens, it is more likely to be done on purpose). As for shadowing function bindings from other packages, this is even more improbable. Please check your facts and avoid posting made-up error messages that can easily be mistaken for actual ones.
- groovy2shoes 10y agoMy example wasn't meant to be in Common Lisp, just a nebulous "Lisp" dialect. See my response to the sibling here: https://news.ycombinator.com/item?id=13223290 https://news.ycombinator.com/item?id=13223290 I'm not trying to be critical of CL. I thoroughly enjoy working in CL. I'm just trying to clear up some confusion about what it means for a macro to be hygienic, that's all.
- junke 10y ago> My example wasn't meant to be in Common Lisp, just a nebulous "Lisp" dialect. And how can I believe you? this is just too late. > I'm just trying to clear up some confusion [...] You were spreading confusion by using a dialect of Lisp known only by you, that looked exactly like CL, without giving proper warnings. > I'm not trying to be critical of CL. I thoroughly enjoy working in CL. Fine, but this would have been the same if you used a nebulous "Scheme" without saying it so.
- groovy2shoes 10y ago> And how can I believe you? this is just too late. Huh? > You were spreading confusion by using a dialect of Lisp known only by you, that looked exactly like CL, without giving proper warnings. It's a hypothetical example meant to illustrate an aspect of macro hygiene. Again, it's not a criticism of CL. Also, it's not "a dialect of Lisp known only by me". It happens to be EuLisp, which is what I've been working with lately, and I chose it because it was designed as a mix between CL and Scheme (and Le-Lisp, RIP) and I figured that, as such, it would be accessible to both Lispers and Schemers. Apparently, I was wrong. > Fine, but this would have been the same if you used a nebulous "Scheme" without saying it so. The specific language was irrelevant to my point. Only the semantics of defmacro and gensym were relevant. EDIT - fixed a quote and some formatting
- kazinator 10y agoOn that last point, you give the semantics of defmacro as defining "a function that receives an s-expression, manually deconstructs and transforms the s-expression". You then exemplify that with (defmacro hypotenuse (x y) ...) which features automatic destructuring that pulls out x and y. The macro needs to do no further manual destructuring on these arguments.
- kazinator 10y agoYour example looks like Common Lisp. The square symbol is bound in the variable namespace by let*; the reference in the macro expansion is for a square in the function namespace. The dual namespace saves most Common Lisp macro-using code from this problem. In large programs, you'd want to use packages. Those also prevent the problem: (defmacro mypackage:macro (arg) `(mypackage:function ,arg)) (flet ((yourpackage:function (arg))) (mypackage:macro (whatever)) yourpackage:function and mypackage:function are different symbols, so no interference. Also, ANSI CL makes it undefined behavior to rebind a standard function; i.e. (labels ((list () ...)) ...) can behave arbitrarily, and implementations are permitted to signal an error like "cannot rebind cl:list".
- groovy2shoes 10y agoMy example is intended to be nebulously "Lisp". I did mean to mention in my comment that Common Lisp's packages help here (I'm usually frustrated in these discussions when people are always like "Lisp2 Lisp2 blah blah" but that's only part of the story, imo packages play a bigger role), so thanks for pointing that out. I did, however, mention that CL doesn't let you rebind standard names, though. My point wasn't "Common Lisp (or defmacro) bad, Scheme good", or that you can't write hygienic macros in Common Lisp[1], though it appears that I've come off that way. My point is just that `gensym` alone isn't enough for hygiene (in all cases). [1]: Another thing that doesn't seem to be understood well enough in these discussions is that hygiene is a property of macros, not macro systems.
- qwertyuiop924 10y agoYou can gensym functions, you know. It's just a PITA. Take it from a CHICKEN user...
- groovy2shoes 10y agoTrue, you can, though I've never seen anyone actually do it. Not that I've read all the code out there or anything, far from it.