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.edulyn@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.edujeff@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.