
Comparative Macrology (2014) - pmoriarty
http://www.wilfred.me.uk/blog/2014/09/15/comparative-macrology/
======
pkhuong
What if x and y may be assigned, but their evaluation has side effects (e.g.,
array[i++])?

Common Lisp's most interesting contribution to this discussion is that places
(~lvalues) are (should be) expressed in a common framework which lets macros
use standard helpers like get-setf-expansion
[http://www.lispworks.com/documentation/HyperSpec/Body/f_get_...](http://www.lispworks.com/documentation/HyperSpec/Body/f_get_se.htm)
to avoid doubly evaluating subexpressions.

------
kruhft
Does the syntax of most programming languages get into the way of macro
systems? Is that why most of the languages in the article are Lisps?

~~~
kmill
It's possible to have metasyntactic variables in languages with more syntax
than a Lisp. Some examples are MetaML, MetaOCaml. Terra (a Lua-like language)
also has metaprogramming[1]. Or TemplateHaskell.

Having a complicated grammar does make it more complicated because you have to
allow metasyntactic variables at different places, vs s-expressions where you
just need a comma (which is just shorthand for the unquote form).

[1] [http://terralang.org/getting-started.html#meta-
programming-t...](http://terralang.org/getting-started.html#meta-programming-
terra-with-lua)

~~~
kruhft
It's possible, but I only really see successful, integrated metaprogramming
done in low syntax languages.

~~~
tempodox
Julia has a macro system that's suspiciously close to CL's.

~~~
kruhft
Quasi Quote looks to be ':', comma is '$', but syntactically, Julia looks
pretty sparse other than mathematical expressions:

Reference:
[https://docs.julialang.org/en/release-0.4/manual/metaprogram...](https://docs.julialang.org/en/release-0.4/manual/metaprogramming/)

But the question is, do people actually do much metaprogramming in Julia?
Unlike CL, which is almost what it's designed for (or something that fell out
of the design), rather than an after thought addition to the language
(although Julia's does look rather well designed or an addition).

------
kazinator
All of these have a problem: they evaluate the places multiple times, which
produces surprising behavior if the place expressions contain side effects and
is inefficient if they invoke expensive computation.

TXR Lisp:

    
    
      (defmacro swap (a b)
        (with-gensyms (ga gb gt)
          ^(placelet ((,ga ,a) (,gb ,b))
             (let ((,gt ,ga))
               (set ,ga ,gb)
               (set ,gb ,gt)))))
    

REPL:

    
    
      1> (sys:expand '(swap (car (g)) (cdr (h))))
      (let ((#:g0255 (g)))
        (let ((#:g0254 (h)))
          (let ((#:g0241 (car #:g0255)))
            (sys:rplaca #:g0255 (cdr #:g0254))
            (sys:rplacd #:g0254 #:g0241))))
    

The place expressions here contain calls to functions _g_ and _h_ ; but as you
can see, these functions are each called just once.

placelet: [http://www.nongnu.org/txr/txr-
manpage.html#N-0393C970](http://www.nongnu.org/txr/txr-
manpage.html#N-0393C970)

placelet lets you easily turn incorrect, multiply-evaluating macrology for
manipulation of places into correct singly-evaluating code while retaining its
basic form. The kernel of the above swap looks like the native three-point
rotation. The only difference is that it's done through the _placelet_ aliases
for _a_ and _b_ , rather than directly with those expressions.

This is just one of the items of my personal research into the advancement of
practical Lisp that I imbued into TXR.

The implementation of _placelet_ is tricky:

[http://www.kylheku.com/cgit/txr/tree/share/txr/stdlib/place....](http://www.kylheku.com/cgit/txr/tree/share/txr/stdlib/place.tl#n891)

The places implementation has no concept of lexically scoped place expansion:
places are are all global. (Just like "setf expansions" in Common Lisp are
global). However, placelet needs to create a scoped place expansion: a lexical
alias for a place. That's why you see the _unwind-protect_ form and the
manipulation of the * place-update-expander * hash. The effectively local
place expander produces getter/setter macrolets which just forward to the real
ones.

Though the hash table is a special variable, this is happening at macro-
expansion time during the tree walk so it is effectively a lexical mechanism:
the dynamic scope of the code walker at expansion time corresponds to a piece
of lexical scope of the code.

For reference, here is the actual _swap_ from the TXR Lisp library (same
source file as _placelet_ ):

    
    
      (defmacro swap (place-0 place-1 :env env)
        (with-gensyms (tmp)
          (with-update-expander (getter-0 setter-0) place-0 env
            (with-update-expander (getter-1 setter-1) place-1 env
              ^(let ((,tmp (,getter-0)))
                 (,setter-0 (,getter-1))
                 (,setter-1 ,tmp))))))
    

It doesn't use _placelet_ but update expanders directly, The placelet aliasing
mechanism is built on this, basically. It produces exactly the same expansion.

This shows how the "update expanders" implementation of places also has the
nice property that you can use update expanders to write straight-forward code
templates to implement place updates.

Update expanders differ from setf expanders in Common Lisp but achieve the
same thing.

(There are also "clobber expanders" for situations when a place is overwritten
without being accessed and "delete expanders": TXR Lisp supports "place
deletion".)

------
lispm
> Macros in CL are just functions that run at compile time

Macros work fine in a Common Lisp interpreter at runtime.

Here we use an interpreter:

Define a macro, which prints a message when it is running.

    
    
        CL-USER 13 > (defmacro swap (x y)
                      (print '(> running the swap macro function))
                      (let ((tmp-sym (gensym)))
                        `(let ((,tmp-sym ,x))
                           (setf ,x ,y)
                           (setf ,y ,tmp-sym))))
        SWAP
    

Use it:

    
    
        CL-USER 14 > (defun foo (a b)
                      (let ((a1 a)
                            (b1 b))
                        (print (list a1 b1))
                        (swap a1 b1)
                        (print (list a1 b1))
                        (values)))
        FOO
    

The macro function wasn't running, otherwise we had it print something.

Run the code. The macro is run, too.

    
    
        CL-USER 15 > (foo 20 30)
    
        (20 30) 
        (> RUNNING THE SWAP MACRO FUNCTION) 
        (30 20) 
    

Run the code again. The macro is run, too.

    
    
        CL-USER 16 > (foo 10 40)
    
        (10 40) 
        (> RUNNING THE SWAP MACRO FUNCTION) 
        (40 10) 
    

Compile the code, the macro now runs, too.

    
    
        CL-USER 17 > (compile 'foo)
    
        (> RUNNING THE SWAP MACRO FUNCTION) 
        FOO
        NIL
        NIL
    

In compiled code the macro is no longer needed to run:

    
    
        CL-USER 18 > (foo 10 40)
    
        (10 40) 
        (40 10) 
    

Note also what pkhuong said, the SWAP macro will run the place forms twice.

Additionally it's possible to observe the change:

    
    
        CL-USER 25 > (let ((a (vector 10))
                           (b (vector 20)))
                       (swap (aref a (progn (print (list :a a)) 0))
                             (aref b (progn (print (list :b a)) 0)))
                       (values a b))
    
        (:A #(10)) 
        (:A #(10)) 
        (:B #(10)) 
        (:B #(20))   ; <- here a is already changed when we get the place value for b
        #(20)
        #(10)
    
    

Common Lisp has a built-in ROTATEF, which takes care of that and which we can
use as a replacement for SWAP:

    
    
        CL-USER 27 > (let ((a (vector 10))
                           (b (vector 20)))
                       (rotatef (aref a (progn (print (list :a a)) 0))
                                (aref b (progn (print (list :b a)) 0)))
                       (values a b))
    
        (:A #(10))    ; <- only once computed in the first subform to ROTATEF
        (:B #(10))    ; <- only once computed in the second subform to ROTATEF
                      ; also the computation of the new value for B does not observe
                      ;  the new value of A
    
        #(20)
        #(10)
    

Thus a 'real' SWAP macro will need to look to be different from what the
article presents. Otherwise it will not play nicely with the rest of the
language: multiple evaluations and side effects are the problems to address.

------
gumby
I have a more substantive comment below, but I have to say the use of
"recurse" grates on me, unless of course you are screaming "fuck, fuck!".

"recur" has the same root as a "occur" \-- in fact it means "to happen again."

occur -> recur occurrence -> recurrence

The thought didn't "occurse to me", it "occurred to me".

Thanks, I just had to get that out!

~~~
kazinator
It's a mess.

The English word "curse" (swearing or evil spell) appears not to be derived
from the Latin "currere" (to run).

These are though: course, current, currency, recur, concur, incur, cursor,
cursory, precursor, cursive, excursion, corsair, corridor, corral, ...

An argument might be made that the verb form of _recursion_ should have
followed the existing _excursion_ pattern. Which is to say, _excursion_ is
both the noun and the verb form, as in _to excursion_ ( _excursioned_ , _had
excursioned_ , _excursioning_ ). Just replace "ex" with "re".

Interesting: etymonline presents a view that _horse_ might also trace back to
_currere_ :
[http://www.etymonline.com/index.php?term=horse](http://www.etymonline.com/index.php?term=horse)

Hey, look; this is is also very handy:
[http://www.etymonline.com/index.php?term=*kers-](http://www.etymonline.com/index.php?term=*kers-)

~~~
gumby
> The English word "curse" (swearing or evil spell) appears not to be derived
> from the Latin "currere" (to run).

Hence my joke about "fuck, fuck"

