A critic of Guido's blog on Python's lambda

Bill Atkins NOatkinwSPAM at rpi.edu
Sun May 7 06:38:34 EDT 2006


"Chris Uppal" <chris.uppal at metagnostic.REMOVE-THIS.org> writes:

> Bill Atkins wrote:
>
>> But why should I have to worry about any of this?  Why can't I do:
>>
>>   (with-indentation (pdf (+ (indentation pdf) 4))
>>      (out-header)
>>      (out-facts))
>>
>> and then within, say out-facts:
>>
>>   (with-indentation (pdf (+ (indentation pdf) 4))
>>     (write pdf "some text"))
>>
>> More readable, and no bookkeeping to worry about.  This is great!  And
>> here's the macro:
> . [...]
>
> Can you explain to a non-Lisper why macros are needed for this ?  I'm a
> Smalltalker, and Smalltalk has no macros, nor anything like 'em, but the
> equivalent of the above in Smalltalk is perfectly feasible, and does not
> require a separate layer of semantics (which is how I think of true macros).
>
>     aPdf
>         withAdditionalIndent: 4
>         do: [ aPdf writeHeader; writeFacts ].
>
> and
>
>     aPdf
>         withAdditionalIndent: 4
>         do: [ aPdf write: '... some text...' ].
>
> Readers unfamiliar with Smalltalk may not find this any easier to read that
> your Lisp code, but I can assure them that to any Smalltalker that code would
> be both completely idiomatic and completely transparent.  (Although I think a
> fair number of Smalltalkers would choose to use a slightly different way of
> expressing this -- which I've avoided here only in order to keep things
> simple).

To be honest, all I know about Smalltalk are the parts of it that were
grafted onto Ruby.  But if your example is doing the same as my code,
then I can't say that there's much of an advantage to using a macro
over this.

>
>> Macros rock.
>
> I have yet to be persuaded of this ;-)

My favorite macro is ITERATE, which was written at MIT to add a
looping mini-language to CL.  There is a macro called LOOP that is
part of Common Lisp, but ITERATE improves upon it in lots of ways.

The cool thing about ITERATE is that it lets you express looping
concepts in a language designed explicitly for such a purpose, e.g.

  (iter (for x in '(1 3 3))
        (summing x))  => 7

  (iter (for x in '(1 -3 2))
        (finding x maximizing (abs x))) => -3

  (iter (for x in '(a b c 1 d 3 e))
        (when (symbolp x)
          (collect x)))  => (a b c d e)

This is a tiny, tiny chunk of what ITERATE offers and of the various
ways these clauses can be combined.  But it should be obvious that
being able to express loops this way is going to make your code much
more concise and more readable.

But it gets even better: since ITERATE is a macro, the code passed to
it is error-checked and analyzed at compile-time, and then converted
to primitive Lisp forms - also at compile-time.  What this means is
you get all this extra expressive power at absolutely no runtime cost.

At the risk of causing brains to explode, here is what the third ITER
form expands into:

(LET* ((#:LIST17 NIL)
       (X NIL)
       (#:RESULT16 NIL)
       (#:END-POINTER18 NIL)
       (#:TEMP19 NIL))
  (BLOCK NIL
    (TAGBODY
      (SETQ #:LIST17 '(A B C 1 D 3 E))
     LOOP-TOP-NIL
      (IF (ATOM #:LIST17) (GO LOOP-END-NIL))
      (SETQ X (CAR #:LIST17))
      (SETQ #:LIST17 (CDR #:LIST17))
      (IF (SYMBOLP X)
          (PROGN
           NIL
           (PROGN
            (SETQ #:TEMP19 (LIST X))
            (SETQ #:END-POINTER18
                    (IF #:RESULT16
                        (SETF (CDR #:END-POINTER18) #:TEMP19)
                        (SETQ #:RESULT16 #:TEMP19)))
            #:RESULT16))
          NIL)
      (GO LOOP-TOP-NIL)
     LOOP-END-NIL)
    #:RESULT16))

This is obviously not something any sane programmer would sit down and
write just to get an efficient loop.  But this code is highly tuned;
for instance, it tracks the end of the list it's building up to save
time.  I, as a programmer, get all this expressiveness and efficiency
for free.  I think that's awesome.

There are lots more examples of macros.  My second favorite macro to
use as an example are Peter Seibel's macros for processing binary files:

  http://gigamonkeys.com/book/practical-parsing-binary-files.html

He builds a set of macros so that in the next chapter he can define an
ID3 tag reader and writer with:

(define-tagged-binary-class id3v2.3-frame ()
  ((id                (frame-id :length 4))
   (size              u4)
   (flags             u2)
   (decompressed-size (optional :type 'u4 :if (frame-compressed-p flags)))
   (encryption-scheme (optional :type 'u1 :if (frame-encrypted-p flags)))
   (grouping-identity (optional :type 'u1 :if (frame-grouped-p flags))))
  (:dispatch (find-frame-class id)))

The macro generates code for methods that let you read and write ID3
files in a structured way - and it can of course be used for any other
kind of binary file.  The :dispatch parameter at the end will jump to
a different class to parse the rest based on the result of executing
the expression attached to it.

There are lots more, too.  Lispers and their environments are often
much more proficient at writing and modifying s-expressions than HTML,
so there are neat HTML generation macros like:

  (<:html (<:head (<:title "foo"))
          (<:body (<:b :style "font-color: orange" "hello!")))

This example uses Marco Baringer's YACLML package.  Again, we get this
expressiveness (no need to use closing tags, the ability to let our
editor indent or transpose tags and so on) for free.  At compile-time,
these macros all get parsed down to much, much simpler code:

 (PROGN
  (WRITE-STRING "<html>  
<head>    
<title>      
foo</title>    
</head>  
<body>    
<b style=\"font-color: orange\">      
"
               *YACLML-STREAM*)
 SOME-VARIABLE
 (WRITE-STRING "</b>    
</body>  
</html>
"
               *YACLML-STREAM*)) 

All of the tags have been streamlined down to a single stream
constant.  Again we get more expressiveness but still keep killer
efficiency.

There are still more!  _On Lisp_ has a lot of interesting ones, like
an embedded Prolog interpreter and compiler:

  (<-- (father billsr billjr))
  (?- (father billsr ?))

  ? = billjr

We have Marco Baringer's ARNESI package, which adds continuations to
Common Lisp through macros, even though Common Lisp does not include
them.  The Common Lisp Object System (CLOS), although now part of
Common Lisp, could be (and originally was) built entirely from macros
[1].  That should give a rough idea of their power.

There are macros like DESTRUCTURING-BIND, which is part of Common
Lisp, but could just as easily have been written by you or me.  It
handles destructuring of lists.  A very simple case:

  (destructuring-bind (a b c) '(1 2 3)
     (+ a b c))  => 6

I hope this gives you a general idea of the coolness of macros.

[1] Strictly speaking, there are a couple of things that the Lisp
implementation has to take care of, but they are not absolutely
essential features.

-- 
This is a song that took me ten years to live and two years to write.
 - Bob Dylan



More information about the Python-list mailing list