You've actually met call by text already. It's used for macros. It's as if the actual text that defines the arguments in a function call is substituted for each occurrence of the formal parameter in the body of the function.
One of the benefits of call-by-text is that if an argument is never actually needed in the called procedure, then no work will be expended evaluating it. As we have seen before, a call-by-text regimen allows us to implement streams.
Rather than provide a syntax-extension for call-by-text, we observe that if you wish to define a function foo that takes arguments x y, and implements call-by-text, all you need do is:
(extend-syntax (foo) ((foo x y) -- write the code for the body of foo here -- -- whenever foo is called the text of the actuals will be substituted for x and y --))
Consider the following code.
You will find that(extend-syntax (double) ((double x) (begin (:= x (+ (^ x) (^ x))) x)))
works just as you would expect. And we might be led to believe comfortably that call-by-text is a fine and dandy convention. However, it is fraught with danger if the evaluation of an argument involves a side-effect.(VAR x 3) (double x) (^ x)
(++ x) has the side-effect of incrementing the value of VAR x by 1. So if you write(define ++ (lambda (x) (:= x (+ (^ x) 1)) x))
you will find that the value of x has increased to 4. If you now type(VAR x 3) (++ x) (^ x)
you might expect that the value of x is now 8. Because you incremented x by 1 with (++ x) and then doubled it with (double __).(VAR x 3) (double (++ x)) (^ x)
In fact, you get 10. Why?
Explain what happened.(VAR x 3) (double (double x)) (^ x)
Some practitioners of the programming language C were believers in the use of the #define macro to improve efficiency over calling really short functions. For example (translating into Scheme syntax), if they wanted a short function square:
they would argue: "Every time I need to square a number, my compiled code will require all the overhead of function calling. It would be much simpler if the pre-processor would replace every occurrence of (square x) by (* x x). In Scheme we would manage this with the folliwng macro.(define square (lambda (x) (* x x)))
C programmers could achieve the same effect with this #define.(extend-syntax (square) ((square x) (* x x)))
As a typical C textbook claims: "When a macro is used, the parameters of the definition are replaced by the actual parameters. A macro that looks like a procedure call can replace procedure calls with in-line code, increasing the efficiency of critical sections of programs".#define square (x) x*x
What the author of this textbook omitted to add was "... but vastly increasing the possibilities for programmer error". Who knows how many debugging hours have been spent by C programmers wondering why a call to square(x++) resulted in x being incremented, not by 1 as the semantics of C would suggest, but by 2?
The newer language C++ has remedied this by introducing inline functions. Whereas when the preprocessor expands a macro it does so without regard to the semantics of the code; on the other hand, when the compiler expands an inline function, it takes into account the semantics. Thus the C++ programmer might write the above as follows.
inline int square(int a) // integer function receiving integer arg by value { return(a*a); }
If the compiler honors the request to expand square inline, no function call should occur in the compiled version of square(x++). Instead, the compiler is responsible for writing inline code that will achieve the same result as a function call. Needless to say, that requires a smarter compiler.