It's not clear to me what you mean by tight coupling or how
it would make it harder to find bugs.
Then you're not qualified to give practical advice about software
development until you've learned more about these things.
So in NetBeans you can specify a multi-line region of code where
perhaps the first or last lines of the code you want to change
do not occupy the whole line and then replace it with something
else ?
In any modern text editor one should be able to do that much. But
gugamilare seemed to be thinking that it's really laborious to make
the same change to the same string in 100 different places. With
reasonably modern tools, that is not the case.
As others have pointed out it's not clear here what gugamilare
meant. Perhaps he meant that you expand the macro , see why it
doesn't work at that specific place in the code , modify the
macro code and then restore the macro call at the place in the
code where you did the expansion.
That's more or less what he said, yes, though the obvious expectation
of an experienced developer is that sooner or later, the final "undo"
step WILL get forgotten by someone, somewhere.
But once again the exact same scenario can happen if you only
use functions. Say you use foo() in 50 different places in your
code but on testing you find that it doesn't achieve the desired
result in one place. So you eliminate the call to foo() at that
particular spot and replace it with something else. Obviously
if later on you change the foo() code it won't affect that
particular place.
Or better, generalize foo() with a new parameter that parametrizes the
way in which the uses differ.
So why is it more dangerous with macros?
Everything's more dangerous with macros.
And as I pointed out above the exact same thing can happen with
functions.
Except that nobody does in-editor inlining of function calls. Not even
as an optimization, these days.
You don't have to, because if the function does the wrong thing the
error's in the function and the function's easy to locate, and if it
doesn't, nothing wacky will be happening. In particular, the
evaluation of the function's arguments is not dependent on the
implementation of the function.
With a macro, on the other hand, expressions in the arguments may end
up evaluated zero or more times in any sequence rather than once each
in left to right order, and changes in the macro's implementation may
change this. If some of the arguments at a particular call site are
expressions with side-effects, the semantics of that code will
sensitively depend on the details of the macro's innards. Furthermore
the macro might be capturing local variables or otherwise interacting
with its environment in ways that functions never do. If a local
variable name happens to clash with a name used internally by the
macro, kaboom! And then you have to do the kludgy expand-in-place
thing to find out what's going wrong, and if you leave it like that,
you've fixed one kaboom but you've got another ticking bomb in exactly
the same spot.
Consider local-variable capture.
Functions: safe, because the function's own local variables are a
separate scope.
Closures: safe; though the closure can alter variables in a larger
scope, the closure's code is lexically contained in that
larger scope, and any unintended interactions are easy to
spot and fix. If the same closure may be evaluated at
many locations, it still can't muck with those locations'
local variables.
Macros: Unsafe. Suppose the macro names some internal variable foo.
Any code that calls the macro and has a local variable named
foo will have problems, and the problems depend on the
innards of the macro. If the macro is changed to make no
outward modification in its normal behavior, but with
internal variable renamings, it might suddenly cause bugs at
call sites that formerly worked. If the macro and the call
site are in separately-managed pieces of code, rather than
one person's code, this is especially problematical.
Now consider side effects:
Functions: safe. Function argument expressions are evaluated left
to right and then assigned to the function's formal
parameters, then the function body is run. Changes to the
number of times and sequence of use of the formal
parameters within the function body have no effect at the
call site, given the function's own return value and side
effects don't change.
Closures: safe. Closures are exactly like functions in this regard,
and though closures can have side effects locally in the
enclosing code, so can the contents of an if branch or a
loop body (and some languages implement these with
closures!).
Macros: Unsafe. Macro arguments are evaluated as they are
encountered in expanding the macro and then executing the
expanded code, so if parameters a, b, and c are used in the
sequence c, a, a, the expressions at the call site are
evaluated in the same order -- so the third argument
expression is evaluated, then the first is evaluated twice,
while the second is never evaluated. If these have side
effects, there's a problem. Furthermore, side effects of
the macro body and of the arguments can be interspersed
arbitrarily. Worse, changes to the macro's implementation
that nominally don't change its semantics can and will
rearrange these side effects.
Worse still, it's hard to fix these problems with macros.
Variable capture: this one is easy to avoid, though the recipe for
avoiding it is easy to forget. In C macros intended to be statements
rather than expressions, you can use a brace-delimited block inside
the macro with its own local variables, for example:
#define FOO(x) {int t = (x); foo1(t); foo2(t);}
will not clobber a local variable named "t" at a call-site, but will
without the braces. Lisp may have an equivalent way of creating a more-
local scope to brace blocks that can be exploited similarly, and if it
does it can probably be used in expression-equivalent macros and not
just statement-equivalent macros, but forgetting to do it will be no
easier to avoid.
Barring a way to create a local scope block inside the macro, the only
way out seems to be to use a global variable. Ugh.
Order of side effects: this one is harder. Again in statement-
equivalent C macros you can use code like the above; in that code,
side effects of x will occur only once and before any side effects of
the macro code, since "int t = x;" has no side effects.
#define FOO(x) foo1(x); foo2(x)
would have run into this problem however, with side effects of x
happening twice, once before and once after the side effects (if any)
of foo1.
Lisp macros surely allow a way out analogous to "t = x;" at the cost
of causing variable capture. As noted above, they *may* provide some
way to prevent variable capture. Both things will be easy to forget,
and the problems caused by macros that forget one or both will be
difficult to diagnose and fix.
If the only way to avoid variable capture is to use a global variable,
though, ugh.
Even if not, there's still yet another problem: consider calling the
following macro:
#define BAR(x,y) {int s = (x), t = (y); foo1(s); foo2(s); foo1(t); foo2
(t);}
in this context:
int s = 17;
int a = 34;
BAR(a,s);
This expands to
int s = 17;
int a = 34;
{int s = (a), t = (s); foo1(s); foo2(s); foo1(t); foo2(t);};
Uh-oh! The intent was surely to invoke
foo1(34); foo2(34); foo1(17); foo2(17);
but the effect in this case will be to invoke
foo1(34); foo2(34); foo1(34); foo2(34);
because the internal local variable s hides the s that's local to the
call site, and it was a parameter. Change the names from a and s to a
and b and the problem goes away, so the problem depends on the names
of the variables near the call site. Arrrrgh!!
Macros in pure-functional languages are only half as evil, since order
of side effects are not a concern, but the problems with variable
capture remain, and as can be seen above simply cannot be made to go
away entirely, except by using global variables with all the problems
that THOSE entail. I doubt most pure functional languages will allow
that dubious escape hatch either, so using one to evade the order-of-
side-effects problem actually makes the variable capture problem
marginally worse.
Only marginally. Once you resort to global variables, you give up
reentrancy for the affected macro, you give up thread-safety or add
some lock contention at every site the macro gets used, and you create
potential name pollution and side effect issues. There's a reason
they're considered harmful.
Macros: the "goto" of the Lisp family of languages.
Now if you want to make the argument
that there are better IDEs available for Java than for Lisp that
may be true but I'm only commenting on whether Lisp macros are
inherently more dangerous than other programming constructs.
So am I, in the extensive discussion immediately above.
And code which calls a function will break if the function
implementation is not correct.
It won't generally be the case, though, that a change to the
function's innards that doesn't change its (nominal) semantics will
break things.
As already explained to gugamilare, if a function takes a and b and
returns the sum of a and 2*b, it doesn't tend to matter (except maybe
for obscure overflow or fp roundoff related cases, and maybe
performance-wise) whether it's implemented as a + b + b or a + 2*b. It
also doesn't matter if the compiler optimizes both cases to a + (b >>
1).
If it's a macro instead, all hell breaks loose. The a + b + b version
causes the expression that supplies b to be executed twice, and
therefore any side effects of that expression to happen twice. For
example, if it's x++, x gets incremented twice instead of once because
of an *implementation detail*. Worse, if a coder expects x to be
double-incremented and accounts for that, then it's recompiled with
aggressive optimizations that replace x + x with x >> 1, the double-
increment goes away and you've got problems again.
Ultimately, the problem is that macros lack encapsulation while
functions don't. The caller of a function doesn't care what names are
used inside a function, nor does the function care what names are used
at any of its call sites. They're not nested scopes, but rather sister
scopes; neither sees the other's locals. There's an encapsulation
barrier of sorts between a function and all of its call sites; except
for intentional side effects, all influences go in through the
argument expressions and out through the return value.
Another form of encapsulation is that the call site code before the
function call runs, then each argument expression runs in left-to-
right order, then the function runs, and then the call site code after
the function call runs. There's a neat separation into blocks: all the
call site code before the function call, then the function call, then
all the call site code after. The timing, number of evaluations, and
sequence of evaluations of call site code depend solely on the
arrangement of the call site code and not one whit on the arrangement
of the code in the function body.
Neither of these holds any more with macros. The argument expressions
and macro code are chopped up, mixed, and evaluated in a sequence
dependent on the macro body. Variables in the macro can either clobber
or hide local variables at the call site, and will do so depending on
the names; changing the names in the macro or at the call site may
alter behavior drastically. Macros lack encapsulation.
Closures also lack encapsulation from their construction site, but the
problem is absent there because these are lexically nested scopes; the
bits of code that are not encapsulation-separated are also physically
proximate in the code base. It's no more of a problem than loop bodies
being able to affect or hide variables in the enclosing function body.
Macros, by contrast, have these same effects *nonlocally*.
If a closure is like a loop body in terms of its effects on code
readability, modularity, and coupling, then a macro is more like a
goto in these terms.
And all of this is *ignoring* the possibility of a macro quietly
replacing a function and altering its semantics, an issue that I
discuss elsethread.
Even if a macro had to use car and cdr to take apart its
argument it could check that the argument has the correct
structure and if it didn't issue an error.
Ah, code that compiles but yields mystery runtime errors, always a fun
way to begin the day.
in which case variable a will be bound to 1, b to 2, etc. So
the Lisp compiler automatically checks that the arguments passed
to the macro have the right structure
What if the argument is a list cons'd at runtime, whose structure the
compiler therefore cannot know in advance? The structure could, say,
depend on the results of a very complex computation.
Actually if in C you do foo(expr1 , expr2) the order in which
expr1 and expr2 will be evaluated is unspecified.
In practice it tends to be left-to-right. It always is in Java. It is
always the case, in either language, that their side effects will all
occur exactly once.
In any case the order of evaluation is precisely specified in Lisp.
I'm sure it is. The problem is that the sequence depends on the
macro's internal guts and not just on the structure of the code at the
call site. That's tight coupling.
But it will influence the order of evaluation of all the expressions
that appear inside the function.
That's not a problem. I don't mind it if the structure of the code in
foo.java affects the order of evaluation of expressions in foo.java. I
just don't want it affecting the order in bar.java, and thankfully,
unless bar == foo or foo.java is actually Sun's Compiler.java, it
won't.
Not so with Lisp.
a = expr1;
b = expr2;
may have different side effects than writing
b = expr2;
a = expr1;
That's fine. The problem is when expr1 and expr2 in
foo(expr1,expr2)
have different side effects than in
foo(expr1,expr2)
because someone changed foo a while ago, rather than because it was
changed to foo(expr2,expr1).
Now the evaluation of code HERE is changed by code over THERE and not
just by code HERE. Do you see what I'm getting at? Subtle problems
might show up here because of changes elsewhere that wouldn't have
changed the semantics of foo IF foo were a normal function, or IF
expr1 and expr2 were free of side-effects.
I don't understand why you're still not getting it. It's gotta be as
plain as the nose on your face by now that macros lack a form of
encapsulation that functions have, and the implementation details of a
macro are necessarily tightly coupled to the code at every call site,
whereas the coupling is much looser between functions and their call
sites.
I've thoroughly torpedoed your repeated claim that macros are "just as
safe as functions". It's taking on water and heading for crush depth.
Abandon ship already!
Now let's imagine that C had a special syntax which allowed you
to decide in which order the arguments of a function get
evaluated. So in a call like foo(expr1 , expr2) you could use
the special syntax to decide if expr1 gets evaluated before
expr2 or vice versa. What makes you think that this would
present any greater programming problems or potential for bugs
than what C actually does offer?
It wouldn't if it were syntax used at the call site. It would if it
were syntax used inside foo's function body in some distant chunk of
code you might not even be allowed to modify, or even have in source
form at all.
It seems to me that order of
evaluation is a central aspect of (non-functional) programming
so I don't see how having one more place where you can influence
order of evaluation makes such a great difference.
The problem is that it influences order of evaluation not locally but
through "spooky action at a distance". In quantum theory, we've
learned to accept that as unavoidable, but keep it the hell away from
my code!
Has someone mentioned variable capture in this thread?
It was mentioned, but that was about it. Until now. Now I have gone
into some detail about it, since nobody else was bothering to go into
any detail about it and since it is another powerful argument against
your "macros are no biggie, really!" nonsense.
Anyway there is a standard and easy way to avoid this
I'm sure there is, but it will have its own problems. I noted two
escape hatches above, each with problems:
1. Create a nested scope and use local variables. Now you only get
trouble if a call site uses a variable of the same name in one of
the arguments to the call, instead of all the time. But you still
can't avoid it entirely. Or:
2. Use global variables, preferably in combination with namespaces.
Of course, all of us here already know why global variables are
only slightly less evil than goto. Even with namespaces.