Call by text

Chapter: Call by text

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.

(extend-syntax (double)
  ((double x)
   (begin
    (:= x (+ (^ x) (^ x)))
    x)))
You will find that
(VAR x 3)
(double 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.

Q. 12
Let us define a function for incrementing a VAR:

(define ++
  (lambda (x)
    (:= x (+ (^ x) 1))
    x))

(++ x) has the side-effect of incrementing the value of VAR x by 1. So if you write
(VAR x 3)
(++ x)
(^ x)
you will find that the value of x has increased to 4. If you now type
(VAR x 3)
(double (++ 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 __).

In fact, you get 10. Why?



Exercise 6

Now type
(VAR x 3)
(double (double x))
(^ x)
Explain what happened.

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:

(define square
  (lambda (x)
    (* x x)))
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.

(extend-syntax (square)
  ((square x)
   (* x x)))
C programmers could achieve the same effect with this #define.
#define 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".

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.


rhyspj@gwu.edu