[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

RE: how expressive are they?



Dan Weinreb wrote:
> I don't think we're going to make much progress talking about small
> examples like IF and :ifNotNil or whatever.  Macros are a heavy-duty
> tool for heavy-duty jobs.

While we have taken the 'if' example about as far as it will go, I see
macros as a wide-spectrum tool for a wide variety of jobs, small and large.
Paul Graham made the point earlier that macros are useful for a language
designer, in that they allow syntax to be easily built on top of a small
core language.  Having developed an abstraction for syntactic transformation
that proves useful to the language designer, it seems as though it might
prove useful to other developers, too.

An example of a small macro which illustrates something that may not be
possible otherwise - and which might be used in more complex scenarios - is
an operation to swap the values of two variables.  For example, Scheme
doesn't have such a feature, but a macro for it takes a few lines:

(define-syntax swap
  (syntax-rules ()
    ((swap x y)
     (let ((tmp x))
       (set! x y)
       (set! y tmp)))))

Now it's possible to use (swap a b) to swap the values of two variables.

Succinct closures don't help here, and nor does "easy syntax for
assignment", except in the sense that you could forget about the macro and
just write the code inline.  In a language that doesn't support call by
reference, a swap would be impractical or impossible without macros.  In
Smalltalk, for example, it may not be directly possible (?)  Someone seems
to have supported this by implementing "reified variables":
http://wiki.cs.uiuc.edu/VisualWorks/Reified+Variables - but there's a *lot*
of code to support it (420-odd lines, although it supports more than just
swapping variable values).  Perhaps Smalltalk could use a macro system? ;)

The swap example also illustrates a point I made earlier about encapsulation
in interfaces.  In C, swap can be implemented as a function which takes two
pointers, e.g. swap(&a, &b).  But why do we need to require that the user
pass through pointers?  It's implicit in the contract of the swap operation
that it modifies the values of the specified variables.  C++ addresses this
by adding reference parameters - piling on yet another specific language
feature.

This illustrates one of the things macros are particularly good at -
constructing a "language feature" that the host language doesn't already
have, and doing so in a way that doesn't require contortions on the part of
the caller.

It's impossible for language designers to predict every possible application
of their languages, but by providing macros, the designer acknowledges that,
and allows the user to implement what they need - and perhaps as
importantly, leave out what they don't need.

Scheme is perhaps the best example of this approach, because of how small
the core language is, and how sophisticated the constructs that can be built
with it are.  It makes an excellent language experimentation toolkit.  The
fact that Scheme doesn't have a module system, or an object system, is not
an obstacle, in theory - you can build them yourself, and quite a number
have of course been built.

This also allows the developer to choose subsystems with a balance of
features appropriate to their problem.  While other languages might not wish
to emulate Scheme's minimalism, the general idea that it's possible to make
some of the most basic characteristics of a language tunable is worthy of
consideration.

Languages that have more syntax, and built-in object systems, built-in
module systems, call-by-reference, etc. may have less need for macros, but
not having macros at all definitely closes off some possibilities.  If one
of a language's goals is to provide maximum flexibility to the developer,
macros are an important feature.  There's a lot to be said for not
attempting to provide every imaginable feature in a language, especially if
the language makes it easy to implement specialized features.

Anton