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

Re: macros vs. blocks



Anton van Straaten writes:
> Can you simulate [building a problem-domain language with macros]
> with objects & blocks?  Sure.  But you can also simulate objects in
> languages without them, like C.  That doesn't mean that language
> support for objects is unnecessary.

I prefer using languages with native object systems, rather than using
an add-on object system in a language like C, for two reasons:

1. Different OO C programs use different object systems, which makes
code reuse unnecessarily difficult.

2. OO programs in C contain more code, and therefore more bugs, than
the same OO programs in OO languages would.

It seems to me that problem-domain languages implemented with blocks
and problem-domain languages implemented with macros will both compose
well with other problem-domain languages implemented the same way, so
item #1 does not map well through this analogy.  Item #2 maps well:
macros allow more brevity.

> Examples of this include the need to quote or not quote certain
> values, the need to construct objects of other classes to pass
> through as parameters, etc.  Macros help make the underlying
> implementation more opaque, and make the interface easier to use -
> and remember.

This reminds me of the argument for reference parameters in C++.
Consider this C routine:

double f(double y) {
  double yprime = sin(y * 3);
  g(yprime);
  return yprime;
}

This routine f(), in C, returns sin(y * 3), but also has some sort of
side effect, which we can't determine without knowing something about
g().  However, in C++, this same function can return anything at all,
because g() could be defined as follows:

void g(double &yprime) { yprime = h(yprime); }

Doing this in C requires the following:

double f(double y) {
  double yprime = sin(y * 3);
  g(&yprime);
  return yprime;
}
void g(double *yprime) { *yprime = h(*yprime); }

g's yprime is a "reference parameter".  Proponents of reference
parameters argue that the caller has no business caring whether the
callee takes its parameter by copy or by reference, and allowing both
to use the same syntax in the caller makes the interface easier to use
and remember.

I disagree; in cases like this, the caller usually cares very much if
the callee could modify its parameter, and forbidding such
modifications brings definite readability advantages, as shown above.
(C++ actually contains another mechanism to forbid this, the 'const'
qualifier, which contains its own unique flaws I don't want to discuss
here.)

Similarly, I do not consider it a boon to clarity that I can't tell
whether (foo (bar baz)) is equivalent to (let ((x (bar baz))) (foo x))
without knowing more about foo.  When someone defined foo as a macro,
it could evaluate (bar baz) immediately, later, repeatedly, or not at
all, or indeed, interpret it in a completely unrelated way.

With Smalltalk blocks, of course, I can tell which parameter
expressions my program will evaluate before it calls the method (the
expressions not in blocks), and which ones the program will pass to
the method for possible later or repeated evaluation (the blocks).

Therefore, on the basis of clarity, I strongly prefer closure-based
interface to a macro-based interface, all else being equal.  

However, macros offer at least the possibility of more brevity; (and x
y z) still contains less punctuation than (and [x] [y] [z]), even
though, in the first case.  you have to know how the language or
library defines 'and' to know that (let ((a y)) (and x a z)) might
behave differently from (and x y z) for some values of y.

Do macros offer *anything* other than shorter programs?

> If Python had macros, it wouldn't really need a project like Mobius
> Python (http://sourceforge.net/projects/mobiuspython), which
> "supplements the existing tokenizer and code generator modules with
> an extensible parser ...  intended for domain-specific Python
> supersets and to prototype extensions to the Python grammar."

Python's syntax contains more than sexprs, so you might want to add
new infix or unary prefix operators or add read-syntax for new data
types.  Scheme-style macros don't suffice for this.

Python has an almost-reasonable substitute for Lisp-style read macros
--- you can write a function that takes a string argument, and that
argument can go on for several lines.  It can even invoke the Python
parser to parse parts of the strings.  I don't think it works all that
well, though.  (I haven't tried it.)

Still, either macros or Smalltalk-style blocks would help a lot with
things like classmethod.  This shocks me:

class foo(object):
    def f(x):
        g(x)
        # 30 more lines of code here, between the beginning of the method
        # and where it's redefined below:
    f = staticmethod(f)

Macros or blocks would help a lot with test cases, GUI handlers, and
similar things, too.

John Clements wrote:
> Hmm.  If you consider the three largest categories of macros [*] to be
> 1) those that affect evaluation order (if),
> 2) those that introduce literal data (quote), and
> 3) those that introduce bindings (let),
> I don't see how block notation solves any other than category (1).

#3 here seems occasionally worthwhile, but of course the same
arguments against control-flow macros apply to binding-introducing
macros, as Trevor Blackwell pointed out.

-- 
<kragen@pobox.com>       Kragen Sitaker     <http://www.pobox.com/~kragen/>
Edsger Wybe Dijkstra died in August of 2002.  The world has lost a great
man.  See http://advogato.org/person/raph/diary.html?start=252 and
http://www.kode-fu.com/geek/2002_08_04_archive.shtml for details.