Did you miss the bit about tight coupling making it harder to find the
bugs?
It's not clear to me what you mean by tight coupling or how
it would make it harder to find bugs.
If your editor doesn't support search and replace, I suggest you
search for a new one to replace it.
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 ? It's quite good if you can do this but it's still less
convenient than macros.
And now you've replaced a bit of your code with the expanded version.
Later you'll change the macro for whatever reason, but at this one
spot the change won't be applied since the macro call was replaced
with its expansion in your editor at that spot. That copy now gets out
of synch with the rest of the code.
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. But let's say instead that you
decide that the macro doesn't work quite right at a specific
place where you originally used it so you have to write the code
explicitly or write another macro for that particular place or
even write a function for that particular place. So yes that
place in the code requires from then on "special treatment", it
won't be affected when you change the macro code.
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. So why is it more dangerous with macros?
The irony being that that was basically the criticism you leveled at
Java and its frequent need for boilerplate code: if I make 100 copies
and later need to change something I have to track them all down. You
may actually have it worse:
1. You write a macro.
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
2. At one point, something goes wrong so you hit a "small sequence of
keystrokes" to do an in-place replacement of a call to that macro
with its expansion.
3. You fix the bug in the macro, whatever it was, but don't change
that other code back into a macro call.
4. Later, you make a change to the macro.
5. Because you didn't copy-paste that code all over the place, you
think you DON'T have to go track down copies and make sure
they're all in synch. As a result, the expanded copy produced
in step 2 doesn't end up reflecting the change.
6. Now you've got code that's getting out of synch with other code.
And as I pointed out above the exact same thing can happen with
functions. An IDE which would help with the issue would work
just as well with macros. 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.
I don't see how it can fail to be. Obviously the code at the macro
call site intimately depends on the macro's innards -- for example, if
one input to the macro is an expression with side-effects, the number
of times the side-effects happen and the order in which side-effects
of different inputs happen will depend heavily on the exact macro
code. So certainly code that calls the macro will break if the macro
implementation (not just its signature) changes.
And code which calls a function will break if the function
implementation is not correct. I'm not sure what you mean by
signature.
The reverse is also
true: if the macro takes the inputs apart with car and cdr to
transform them in some way, its correctness will depend on the inputs
having the correct structure.
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. But Common Lisp
macros do not have to use such lowly means. Consider the
following definition:
(defmacro m (a (b c) (d e (f g)))
`(list ,a ,b ,c ,d ,e ,f ,g))
What the macro does is trivial, it just prints its arguments but
the point is to show how the definition of the macro can specify
the structure of its arguments. If I call the macro like
(m 1 2 3 4 5 6 7)
or
(m 1 (2 3) (3 4 5 6))
I will get an error message. I have to call it like
(m 1 (2 3) (3 4 (5 6)))
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 and binds each variable
appearing in the macro definition to the corresponding part of
the arguments given to the macro.
So, the macro definition and every call site of the macro will be
tightly coupled.
A Java method or C function definition is more loosely coupled. Since
it's just values of some sort passed as parameters, rather than code,
the order of side-effects of the arguments is well-defined: each
happens once, in left-to-right-order, at the call site and just before
the call itself.
Actually if in C you do foo(expr1 , expr2) the order in which
expr1 and expr2 will be evaluated is unspecified. In any case
the order of evaluation is precisely specified in Lisp. The
appropriate parts of the code passed as an argument to the macro
will be assigned to the macro variables and what happens from
then on depends on what the macro does. It can decide which code
to evaluate and in which order.
With pass-by-value (Java primitives and non-pointer C
parameters) the function also won't alter the passed-in data. With
pass-by-reference (Java objects with mutable state, pointers, C++
references) the function can alter the passed-in data but its
implementation still won't influence side effects and order of
evaluation of the argument expressions.
But it will influence the order of evaluation of all the expressions
that
appear inside the function. In other words writing
a = expr1;
b = expr2;
may have different side effects than writing
b = expr2;
a = expr1;
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 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.
Macros may be able to do powerful things, but with that power comes
the greater potential for problems. (Others have mentioned variable
capture, so I won't go into detail about that here.)
Has someone mentioned variable capture in this thread? I missed
this. Anyway there is a standard and easy way to avoid this
(unless of course you want variable capture to happen and on
some occasions you do) which is explained in chapter 8 of
``Practical Common Lisp''.
< Snip comments about modifying running images >
--
He could sometimes be caustic in his reminiscences of the
South during that era, once writing that "even in the 'Bible
Belt' the Bible is a relatively unknown book -- sacred, of
course, but quite unfamiliar."
Refers to theologian Langdon Gilkey Dies