[comp.lang.scheme] Why macros impair readability

lyn@altdorf.ai.mit.EDU (Franklyn Turbak) (03/05/91)

This is a long message on why macros impair readability.  Read the
abstract for a summary.  I'm interested in hearing feedback, alternate
models, and lots of anecdotes and examples.  

- Lyn -

---------------------------------------------------------------------------

		    WHY MACROS IMPAIR READABILITY
					
			   Franklyn Turbak
			    March 4, 1991


ABSTRACT
--------

Arguments about program readability are often based on many implicit
assumptions about the definitions of "reader" and "readability".  Such
arguments would be more compelling if (1) these definitions were made
explicit and (2) the arguments were based on technical considerations
in terms of concrete examples.  Using an interpretation where
"readable" means "supporting local reasoning about program fragments",
I discuss four reasons why macros impair readability in Scheme:
  
  1. Applicative Order Evaluation
  2. Static Scope
  3. Procedures as First-Class Objects 
  4. Debugging

I conclude with an entreaty for alternate analyses and anecdotes of how
macros help/hinder code readability.

WHAT IS READABILITY?
--------------------

  In the recent debate on the Scheme mailing list about the advantages
and disadvantages of macros, a number of arguments were made about
"readability" of code containing macros.  But the notion of
readability is rather slippery.  First of all, "readable" code admits
many possible interpretations, including:

    * Concise - free of baggage not important to the ideas being expressed.

    * Understandable - matched with the reader's mental structures.

    * Expressive - accurately conveys the writer's mental structures.

    * Modifiable - structured to permit extensions and variations.

    * Well-documented - contains helpful descriptions, comments, names.

    * Verifiable - aids in the proof (by people or machines) of properties 
                   of the described process.

    * Recallable - easy to remember or rederive.

    * Teachable - explainable to someone else.

  Second, all these  interpretations depend a heck of a lot on who  the
reader is.  Readers vary widely in background, programming skill, and
purpose for reading the code.  A "most readable" style is a fiction;
what is clear and concise to some readers may be inscrutable to others.
Many people can (and do) agree on matters of programming style; nevertheless,
different styles are tuned to different kinds of readers.  For example:

    * A WHILE macro may be a boon to an imperative thinker and anathema 
      to a functional thinker.

    * Extensive documentation that aids some readers gets in the way of other 
      readers who want to see more of the code in single editor buffer.  

    * Conventions like thunking args to delay evaluation, implementing 
      message-passing objects as procedures, or using continuation-passing
      style to achieve nonstandard control flow are clear as day to those 
      facile with these techniques, but (1) pose difficulties to those
      not familiar with these devices and (2) are candidates for abstraction
      by those who believe such details obscure the essence of the code.

    * An interpreter using concrete rather than abstract syntax is 
      well-suited for class presentation because it is shorter and is more 
      likely to fit in its entirety on a blackboard.  On the other hand, 
      a version with abstract syntax may be better suited to lab study, 
      where the code readers may want to implement an alternate syntax.

    * For a person who simply wants to use a given program, a description 
      of its interface & behavior and clearly marked entry points are 
      crucial.  For someone attempting to extend a program, hierarchical
      structure and accessibility of "hooks" are important.  Clear structuring
      of data and control flow are essential for readers who want to understand
      particular algorithms.


TOWARDS A MORE OBJECTIVE ANALYSIS OF READABILITY
------------------------------------------------

  Given the above, it's easy to see how discussions about readability
can easily degenerate into religious squabbles.  If everyone assumes
his/her own interpretation of "readable" and "reader", then people
aren't really debating the same issue.  One way to improve the
situation is for discussants to be more explicit about their
assumptions.  Another improvement would be the use of specific examples
rather than vague generalities.  "This particular macro
improves/impairs readbility because ..." is much more convincing than 
nebulous claims about factors enhancing or detracting from readabilty.

  But even more desirable would be arguments with a more formal or
objective basis for comparison.  In light of this goal, I consider
four linguistic issues to illustrate why Scheme code using macros can
less be readable than Scheme code without macros.  Here, I use the
term "readable" to mean "easy to reason about locally", where locally
refers to the fact that certain conclusions can be made about a code
fragment without knowing the full context in which it occurs.  I also
assume that the program being read is a large one, so that there is a
nontrivial overhead to obtaining global information, such as finding
top-level definitions.  Local reasoning is particularly valuable in
such situations.  Finally, I assume that the reader desires a detailed
understanding of the code, not just a feel for it's high-level structure.

1. APPLICATIVE ORDER EVALUATION 
-------------------------------

   A common use of macros is to simulate normal order evaluation of arguments
   within Scheme's applicative framework.  For example, it is possible
   to implement lazy pairs by the desugarings:

      (LAZY-CONS <exp1> <exp2>) => (CONS (LAMBDA () <exp1>) (LAMBDA () <exp2>))

      (LAZY-CAR <exp>) => ((CAR <exp>))
      (LAZY-CDR <exp>) => ((CDR <exp>))

   (Both LAZY-CAR and LAZY-CDR could be procedures, but LAZY-CONS must be
   a macro.)

   Although using macros in this way can reduce the clutter of thunks, it
   makes it more difficult to reason about the evaluation of expressions
   that appear in the argument positions of a procedure/macro call.
   In macro-less Scheme, for example, the expression

      (unknown (letrec ((loop (lambda () 
                                (loop)))) 
                 (loop)))

   must be nonterminating regardless of the meaning of UNKNOWN because 
   all arguments must be evaluated before the procedure is called.  But 
   in the presence of macros, an argument expression may be evaluated 
   zero times, so the above could return a value.  Macros
   require the reader to use more global knowledge to understand this 
   fragment.

   Similarly, with macros an argument expression might be evaluated
   more than once.  This can wreak havoc in the presence of side effects.
   In the expression

      (let ((x 0))
        (unknown (begin (set! x (+ x 1)) 
                        17)))

   X is incremented only once in macro-less Scheme, but might be 
   incremented any number of times depending on the definition of 
   UNKNOWN, if it were a macro.

   Granted, the above examples are contrived, and it is generally
   considered bad policy to have side-effects in argument positions.
   Nevertheless, the same problems can crop up in much more natural situations.
   The point is that local reasoning valid in a purely applicative-order
   language is no longer necessarily valid in the presence of macros.

   Note that this problem is ameliorated by Aubrey Jaffer's suggestion
   of distinguishing macro names from procedure names (or macro calls from
   procedure calls).  In that case, the usual Scheme reasoning can be 
   used in the vast majority of the cases (procedure calls), but the 
   potentially troublesome cases are syntactically flagged.

2. STATIC SCOPING
-----------------

   The kind of lexical reasoning enabled by Scheme's static scope can be 
   invalidated in the presence of macros.  Consider the expression:

       (let ((return (lambda (n) (* 2 n))))
         (block 
           (+ 100 (return 3))))

     If BLOCK were a procedure, then the RETURN that appears within its
   argument would have to refer to the multiply-by-two procedure, and the
   meaning of the expression would be the same as that of 

       (block 106)

   Of course, we couldn't say more about the meaning of the whole expression
   until we also knew more about the behavior of the BLOCK procedure. But
   it would still be possible to make a firm conclusion about the value of 
   BLOCK's argument without any more global information.

     In the presence of macros, all bets are off, since BLOCK might be a 
   macro that (intentionally) binds the name RETURN within its scope.
   E.g., a desugaring for BLOCK might be:

       (BLOCK <exp>) => (CALL-WITH-CURRENT-CONTINUATION
                          (LAMBDA (RETURN) <exp>))

   (Note this naming issue is different than the "accidental name capture"
   problem associated with faulty macro implementations.  Here the 
   macro writer really wants the name RETURN to be captured.)

   The BLOCK macro might even treat its entire argument as text:

       (BLOCK <exp>) => (QUOTE <exp>)

   Here, the name RETURN is just a symbol, and not a variable
   reference after all.

   Here we have a situation where the introduction of macros has the
   potential of complicating the scope rules that programmers use to 
   perform local reasoning about their programs.  Counterarguments to 
   this point are:

     (1) Such examples are extremely rare.

     (2) Macros that intentionally bind names should not be allowed.

   But the fact remains that in the presence of macros, the *possibility*
   that names might not have their normal lexical interpretation must
   at least be considered by the reader.

   Again, Jaffer's proposal to syntactically distinguish macros from 
   procedures would clearly delineate those regions of code where the
   usual reasoning about scoping might not apply.


3. PROCEDURES AS FIRST-CLASS OBJECTS
------------------------------------

   Scheme encourages programmers to exploit the first-class nature of 
   procedures.  Thus, procedures are commonly named, passed as arguments,
   returned as results, and stored in data structures. A disadvantage
   of macros is that they cannot be treated in this way.  For example,
   AND is commonly treated as a macro with the desugaring:

     (AND <exp1> <exp2>) => (IF <exp1> <exp2> #f)

   There are situations where it is desirable to pass AND as an argument:

       (define (accumulate combiner null-value lst)
         (if (null? lst)
             null-value
             (combiner (car lst)
                       (accumulate combiner null-value (cdr lst)))))

       (define (all-true? lst)
         (accumulate and #t lst))

   Unfortunately, this doesn't work; the AND must be encapsulated into 
   a procedure before it can be passed:

       (define (all-true? lst)
         (accumulate (lambda (x y) (and x y)) #t lst))

     This problem is not so much one of macros destroying local reasoning
   properties but rather one of verbosity and inconsistency.  Still,
   without knowing the definiton of AND, a reader modifying code cannot
   safely replace (LAMBDA (X Y) (AND X Y)) by AND.  Such a local 
   modification would be valid in macro-less Scheme. 

     Yet again, a convention flagging macro names would alleviate the
   situation.
   
4. DEBUGGING
------------

     Though macros may aid in making source code more concise, the
   macro-expanded code can often be rather unwieldy.  The expanded
   code is normally hidden from the reader, but often rears its ugly head
   during debugging. 

     Consider an example from the Mini-FX programming language 
   used in the graduate programming languages course at MIT.
   (Mini-FX is a simplified version of Dave Gifford's FX language
   implemented as a macro package on top of Scheme).  Mini-FX supports
   a powerful pattern matching construct called MATCH. Below is 
   an example where MATCH is used in the definition of a list reversal
   procedure:

      (define (reverse lst)
        (match lst
          ('() '())
          (`(,first ,@rest) (append (reverse rest) (list first)))))

   My goal here isn't to describe the semantics of MATCH, but simply
   to show that it can lead to extremely complex macro expansions.
   The above definition expands into:

      (define (reverse lst)
       (if (equal? lst '())
           '()          
           (let ((#fail-25 (lambda ()
                             (error 
                              (string-append "MINI-FX RUNTIME ERROR (This error should be caught by the typechecker!):\n" 
                                             "MATCH -- no pattern matched")
                              lst))))
             (list->sexp~ lst
               (lambda #success-arg-27
                 (if (not (= (*minifx-length* #success-arg-27) 1))
                     (*minifx-success-number-of-args-mismatch* 
                      '((cons~ first rest))
                      #success-arg-27)
                     (apply
                      (lambda (#temp-26)
                        (cons~ #temp-26
                          (lambda #success-arg-28
                            (if (not (= (*minifx-length* #success-arg-28) 2))
                                (*minifx-success-number-of-args-mismatch*
                                 '(first rest)           
                                 #success-arg-28)
                                (apply
                                 (lambda (first rest)
                                   (append (reverse rest) (list first)))   
                                 #success-arg-28)))
                          #fail-25))
                      #success-arg-27)))
               #fail-25))))

      A user who makes an error within a MATCH clause will be thrown into
   a debugger that has access to the verbose expanded code but not the concise
   unexpanded code. Here we have yet another kind of code reader facing
   locality difficulties of a different sort. In this case the expressive 
   advantages offered by syntactic abstraction have disappeared, and the reader
   is left with the job of matching up the expanded code with the appropriate
   section of the source code. Had procedural abstraction been used instead,
   this matching up process would be greatly simplified.

      This problem seems less intrinsic than the others because it seems
   possible to design a "smart" debugger that would aid in the inverse
   of macro expansion (= macro contraction?).  Nevertheless, in Scheme
   systems I have seen, the above problem is very real one for the
   reader-as-debugger.

      Note that this problem is due to the very nature of macros; Jaffer's
   syntactic distinction scheme will not help here.


DISCUSSION
----------

  Please note that I am *not* claiming that Scheme without macros is
inherently readable.  Such a claim is absurd, because it is possible
to write bad programs in any language.  And as Mark Friedman and
others have pointed out, there are many reasons why macro-less Scheme
can be hard to read.

  I also do not claim that introduction of macros into a program
always makes it less readable. There are many situations where a
judicious use of macros makes code more understandable by abstracting
over the particular mechanism that implements a behavior.
(Unfortunately, macros are notoriously hard to write well; macrology
is quite a black art.)

  What I *am* claiming is that macros introduce a new set of
*potential* reasoning difficulties in addition to the ones that are
already present in macro-less code.  Whether these difficulties
*actually* impair programmers' reasoning in practice is an empirical
issue.  Many of the above examples are simple and contrived.  I'd like
to hear about specific cases where people think these (or other)
issues were at play in macros hindering their reasoning.  

  Note that in three of the four points raised above, Jaffer's
syntactic distinction idea seemed well-motivated.  This conclusion is
based on my particular assumptions.  Of course, there are other
assumptions and models under which macros improve reasoning and
macro/procedure syntactic distinction unduly complicate programs.  I
entreat people to make such assumptions and models explicit in their
readability arguments, and to make liberal use of examples in illustrating
their viewpoints.

markf@zurich.ai.mit.edu (Mark Friedman) (03/07/91)

First let me say that I applaud Franklyn's attempt to unravel and
explicate the issues involved Scheme code readability. I also marvel
at and envy the clarity of his writing. 

OK, now that I have buttered him up ... 

I think that his arguments are sound but his premise is faulty.

In particular, after presenting many reasonable interpretations of the
term "readable" he chooses the least useful. He uses the term to mean 
"easy to reason about locally". The problem is that for large (and
well abstracted) programs local reasoning gets you very little
information. For example, consider the following procedure:

  (define (foo bar)
    (mumble ((grumble bar)
             baz
             fumble)
     zaz)
    grok
    (((bar))))

Would it really help to know that grumble is a macro?  Unless you know
what grumble really does (or at least what it is supposed to do) the
issue of whether it is a macro or a procedure is fairly moot.  And in
the process of discovering what grumble actually does you will find
out whether it is macro or a procedure.

Now admittedly, macros are, on the whole, harder to figure out than
procedures, but that is not a issue of local readability. A
sufficiently complex macro (or procedure) must be well documented.
Higher level macro decription languages like extend-sytax help a lot
here also.

Your specific arguments against macros are well taken, but they seem
to imply a certain naive view (not that I think that Franklyn is naive
in any way :-) of the Scheme evaluation model. Scheme
is not the lambda calculus and it's not referentially transparent.
Macros do add extra comlexity, but I don't feel that they
qualitatively change the rules of the game. More importantly, they
give us another powerful abstraction mechanism. 

-Mark
--

Mark Friedman
MIT Artificial Intelligence Lab
545 Technology Sq.
Cambridge, Ma. 02139

markf@zurich.ai.mit.edu

lyn@altdorf.ai.mit.EDU (Franklyn Turbak) (03/08/91)

This a response to Mark Friedman's evaluation of my analysis of macro
readability:

     > I think that his arguments are sound but his premise is faulty.
     >
     > In particular, after presenting many reasonable interpretations of the
     > term "readable" he chooses the least useful. He uses the term to mean 
     > "easy to reason about locally". The problem is that for large (and
     > well abstracted) programs local reasoning gets you very little
     > information. 

I agree wholeheartedly; my definition of "readability" *is* weak and not
very useful.  However, it's the only one I could think of at the time
of writing that allowed me to raise the set of points I wanted to raise:
that macros complicate reasoning about order of evaluation, static scope, 
first-classness, and debugging. 

I'd rather have a much stronger notion of readability that more people
find in tune with their intuitions.  From the discussions so far,
there's a lot of people who *do* feel that macros can make programs
hard to read.  My goal is to understand better *why* they feel this
way.  

Over the years, I personally have come to view the macro
as a kind of powerful drug; its judicious use can improve programs,
but its abuse leads to programs that are hard to read.  But why do I 
feel this way?  I assume it's because I've gotten "burnt" too many 
times when trying to read code that contains lots of macros.  I wish 
I had written down or could remember all these situations --- then I'd
have at chance at analyzing exactly what the difficulties were and
how my feelings about macros developed.  Maybe the macros tended to
introduce gratuitous syntax,  Maybe they were often written or documented
poorly.  Maybe I just didn`t have a good enough model of macros to 
understand what they meant or why they were being used.  Unfortunately,
I don`t remember these situations, and am left with the task of
rationalizing my feelings.  

My analysis was only a first attempt at this task. It is not
convincing in many ways and needs to be improved.  I'm hoping that
further discussion on this mailing list will elucidate the 
fundamental issues involved in the effects of macros on readability.

     > For example, consider the following procedure:

     > (define (foo bar)
     >   (mumble ((grumble bar)
     > 	          baz
     >            fumble)
     >           zaz)
     >   grok
     >   (((bar))))

     > Would it really help to know that grumble is a macro?  Unless you know
     > what grumble really does (or at least what it is supposed to do) the
     > issue of whether it is a macro or a procedure is fairly moot.  And in
     > the process of discovering what grumble actually does you will find
     > out whether it is macro or a procedure.

     > Now admittedly, macros are, on the whole, harder to figure out than
     > procedures, but that is not a issue of local readability. A
     > sufficiently complex macro (or procedure) must be well documented.
     > Higher level macro decription languages like extend-sytax help a lot
     > here also.

Yes, the local reasoning criterion is weak.  The key to a more usable
definition involves formalizing your intuition that macros "are harder
to figure out than procedures".  What makes them harder to figure out, 
and how does this impact on readability?

I am working on an improved model of "readability" to better handle
your objection.  The basic idea is that macros are harder than
procedures for the reader to model behaviorally because of the various
funky things that can be done in complex desugarings.  So
understanding (and remembering for later use) macro definitions and
calls requires more cognitive overheard than procedure definitions and
calls of similar syntactic complexity.  Maybe more limited versions
of syntactic abstraction (like EXTEND-SYNTAX) are sufficiently 
constrained to be more amenable to behavioral modelling than general
macro facilities.

Obviously, there are a lot of details to be worked out here.  Does 
anyone else who believes that macros impair readability have any 
ideas along these lines?

     > Your specific arguments against macros are well taken, but they seem
     > to imply a certain naive view (not that I think that Franklyn is naive
     > in any way :-) of the Scheme evaluation model. Scheme
     > is not the lambda calculus and it's not referentially transparent.
     > Macros do add extra comlexity, but I don't feel that they
     > qualitatively change the rules of the game. More importantly, they
     > give us another powerful abstraction mechanism. 

You say that macros are just another abstraction mechanism that do not
qualitatively change the rules of the game.  But then why is it that
syntactic abstraction and procedural abstraction evoke such different
responses with regard to readability.  I rarely (if ever) hear
programmers complaining that code was hard to read because it
contained procedures, but *do* hear programmers making these kinds
of claims about macros.

- Lyn -

markf@zurich.ai.mit.edu (Mark Friedman) (03/08/91)

In article <9103071300.aa28959@mc.lcs.mit.edu> lyn@altdorf.ai.mit.EDU
(Franklyn Turbak) writes:

   This a response to Mark Friedman's evaluation of my analysis of macro
   readability:

   I agree wholeheartedly; 

No no Alphonse, I agree with you wholeheartedly :-)

   You say that macros are just another abstraction mechanism that do not
   qualitatively change the rules of the game.

What I meant was not that macros are JUST another abstraction
mechanism, but that there are other things in Scheme which complicate
the rules (e.g. side effects and call-with-current-continuation). We
accept them (and sometimes scorn them) because of their power, just as
we should accept (and sometimes scorn) macros.

   But then why is it that syntactic abstraction and procedural
   abstraction evoke such different responses with regard to
   readability.  I rarely (if ever) hear programmers complaining that
   code was hard to read because it contained procedures, but *do*
   hear programmers making these kinds of claims about macros.

I think that Scheme programmers are more aware of the pitfalls of
things like side effects and call-with-current-continuation than they
are of macros. To that end, this whole discussion (especially your
part) is incredibly useful. 

If you look at code written in Scheme (or more likely Common Lisp)
with a "Fortran style" it is often very difficult to read. The
combination of procedural abstraction with side effects to global
variables can be deadly. A former Fortraner could (weakly) argue that
the problem is the modularization into procedures. The code would
probably be clearer is it were clumped together because we sort of
expect that separate procedures act upon separate local state. Of
course a Schemer would (strongly) argue for getting rid of the global
variables. 

-Mark
--

Mark Friedman
MIT Artificial Intelligence Lab
545 Technology Sq.
Cambridge, Ma. 02139

markf@zurich.ai.mit.edu

kend@data.UUCP (Ken Dickey) (03/09/91)

In comp.lang.scheme you write:

>...  I rarely (if ever) hear
>programmers complaining that code was hard to read because it
>contained procedures, but *do* hear programmers making these kinds
>of claims about macros.

>- Lyn -

...Probably because more people are given training in what is
stylistically appropriate.  This is expressed as "the learning curve
is steep".  Once passed the curve we understand procedures and don't
have a problem reasoning about them (much).  

I feel that the same is true of syntactic transformations.  Many (most?)
people have not gone through the learning curve and write "half-baked"
macro code.  This is exacerbated when low-level macro interfaces are
used, as the "rewrite rules" are not textually apparent.  IMHO this
precisely parallels the case of going from a "high-level" language to
assembler--it takes a person knowledgeable in representation and call
pattern usage to do the appropriate thing.  The low-level is harder to
understand, harder to debug, etc.

-Ken Dickey				kend@data.uucp

markf@zurich.ai.mit.edu (Mark Friedman) (03/09/91)

You know that something's wierd when you reply to your own response. I
realize that I have boxed myself into a corner with my "macros DON'T
impair readablity" statements. I will now try to wrestle myself out of
the corner with a new statement: 

  Macros themselves are unreadable. 

I think that this is more the crux of the matter than Jaffer's
complaints about not knowing when something is a macro or Turbak's
complaints about the dangers of using macros.

Consider a procedure like:

(define (foo bar)
  (let ((baz (grumble bar)))
    (mumble (stumble baz) (fumble baz))))

We like to say something like:

First set baz to grumble of bar then compute stumble of baz and fumble
of baz and then apply mumble to those.

Now consider:

(define-macro (foo bar)
  (let ((baz (grumble bar)))
    (mumble (stumble baz) (fumble baz))))

Now the the same description applies, but it is not a semantically
meaningful description of what foo does. It is a description of the
construction of something which does foo. It is the wrong (for us)
level of description.  What's more, the more we modularly decompose or
macro definitions the harder things get. The program fragments get
less meaningful and the combining process gets more complex. This, of
course, is not susprising. After all macros don't specify (run time)
processes, they specify program constructions which specify processes.

Procedures, however, do describe processes - or do they?

Well at one level, yes they do. You plug the definition of the
procedure into the Scheme evaluation rules and a process ensues. But
an interesting question - and the one that is relevant to the
readability issue - is: Does a Scheme procedure definition describe a
process that we can understand. That is, does our idea of a process
match Scheme's idea of a process.  I think that for many of us the
answer is no, and the reason is similar to the reason why macros are
hard to understand.

Consider the following program snippet:

(define (tough foo)
  (((((foo 10) 'bar) 12.2) 'wow) 0))

(define (foo bar)
  (foo2 (whoknows bar)))

(define (foo2 bar)
   (lambda (x)
     (foo3 (mumble x) (rumble bar))))

(define (foo3 bar baz)
  (lambda (x)
    (lambda (y)
      (stumble x y (lambda (foo (zaz bar))) (baz)))))
...

Many people find programs like these hard to understand. A large part
of the reason is that they construct and combine procedure fragments
(i.e. lambda expressions). These procedure fragments are similar to
the program fragments created and combined by macros.  Perhaps most
importantly, the order of execution of these fragments is not
immediately apparent. They may get stored for awhile or passed around
for awhile before they are applied. This again is similar to the
situation with macro definitions where the program fragments may be
arbitrarily combined.

The question still remains however of why these things are hard to
understand. After all the Scheme evaluation rules are fairly simple. I
haven't even used side effects or call-with-current-continuation or
even an IF in the above examples. I don't really have an answer for
this but I suspect that many of us have an essentially serial,
imperative, limited stack (and heap) mind set that doesn't match very
well with the evaluation of procedures like the ones given and complex
macro constructions.

-Mark
--

Mark Friedman
MIT Artificial Intelligence Lab
545 Technology Sq.
Cambridge, Ma. 02139

markf@zurich.ai.mit.edu

jeff@aiai.ed.ac.uk (Jeff Dalton) (03/10/91)

The intention is to add macros, and its just a matter of unfortunate
timing that they're not there already.  That it is unfortunate is
shown by (among other things) the recent messages arguing against
macros.  If Scheme already had macros, we'd be able to spend our
time discussing how to write good, readable macros instead.

Anyone who doesn't like macros or thinks they're unreadable doesn't
have to use them.  They'll have a choice.  On the other hand, if
they're not in the language those of us who want to use them can't.
No choice, unless we want to write non-portable code.

In article <9103041928.aa21342@mc.lcs.mit.edu> lyn@altdorf.ai.mit.EDU (Franklyn Turbak) writes:

>  Given the above, it's easy to see how discussions about readability
>can easily degenerate into religious squabbles.  If everyone assumes
>his/her own interpretation of "readable" and "reader", then people
>aren't really debating the same issue.  

I disagree almost completely that that is the problem.

Of course, some people do have strange notions of what "readable"
means.  For example, you seem to think it should mean "easy to
reason about locally".

I think that in part you are confusing the question of the definition
of readability with that of what counts as a good reason for claiming
something is readable.

Readability has an inherant subjective component, but it is not
entirely subjective.  In particular, if there's a question about
whether something is readable, there can be good reasons for saying
it is or is not.  It is not just a matter of saying "I find it easy
to read, so it's readable".  "Easy to reason about locally" would
count as a good reason.  That doesn't mean we should consider it
a _definition_ of readability, however.

>  I also do not claim that introduction of macros into a program
>always makes it less readable. There are many situations where a
>judicious use of macros makes code more understandable by abstracting
>over the particular mechanism that implements a behavior.

Yes, but you want to create the impression that on the whole
macros make things worse as far as readability is concerned.

And note that here you're going back to what we might call the naive
notion of readability -- or are you saying that macros sometimes make
it easier to reason locally?

>(Unfortunately, macros are notoriously hard to write well; macrology
>is quite a black art.)

Some macros are hard to write well, others are not.

>  Note that in three of the four points raised above, Jaffer's
>syntactic distinction idea seemed well-motivated.  This conclusion is
>based on my particular assumptions.  Of course, there are other
>assumptions and models under which macros improve reasoning and
>macro/procedure syntactic distinction unduly complicate programs.  I
>entreat people to make such assumptions and models explicit in their
>readability arguments, and to make liberal use of examples in illustrating
>their viewpoints.

That's certainly a way to make it as hard as possible for someone to
argue that macros are readable.

I hope that we don't have to make such arguments at all, and that
macros will be added to Scheme regardless.

N.B. I agree that macros introduce new problems for local reasoning
and with the parts of your article devoted to that point.

-- jd

jeff@aiai.ed.ac.uk (Jeff Dalton) (03/10/91)

In article <9103071300.aa28959@mc.lcs.mit.edu> lyn@altdorf.ai.mit.EDU (Franklyn Turbak) writes:

>I agree wholeheartedly; my definition of "readability" *is* weak and not
>very useful.  However, it's the only one I could think of at the time
>of writing that allowed me to raise the set of points I wanted to raise:
>that macros complicate reasoning about order of evaluation, static scope, 
>first-classness, and debugging. 

There was a much easier solution, namely to raise those points
without trying to tie them to the definition of readability.

>I'd rather have a much stronger notion of readability that more people
>find in tune with their intuitions.  From the discussions so far,
>there's a lot of people who *do* feel that macros can make programs
>hard to read.  My goal is to understand better *why* they feel this
>way.  

I'd agree with that.

But one reason is that macros are often written as fairly complex list
structure manipulations.  Backquote helps some, extend-syntax much more.
There are a number of simple reasons like this, that don't require a
deep analysis.

>I am working on an improved model of "readability" to better handle
>your objection.  The basic idea is that macros are harder than
>procedures for the reader to model behaviorally because of the various
>funky things that can be done in complex desugarings. 

N.B. _can_ be done.  The complaints ought to be about the funky
things. 

Note too that a desugaring can be complex without the macro being hard
to understand if it implements a well-defined, easy to manage abstraction.
For example, I can understand list comprehensons in Haskell more
easily than I can understand the desugaring.