Eager Evaluation and Macros
Eager evaluation is when an expression is evaluated as soon as it is assigned to a variable. Most languages are eagerly evaluated because any side-effects occur immediately, rather than prolonging them along execution.
However, there are situations where eager evaluation works against a program.
Suppose there is a need for implementing new versions of the boolean operators:
(define (my-or p q) (if p #t q)) ; (my-or #t (/ 0 0)) ; => ERROR
my-or is unachievable because short-circuiting does not occur due to eager evaluation; the illegal operation division by zero takes precedence. Similarly, the same situation is present when implementing
(define (my-and p q) (if (not p) #f q)) ; (my-and #f (/ 1 0)) => ERROR
The errors in both these functions are produced due to runtime exceptions. To solve this, Racket macros will come in handy.
Macros are patterns in code which get replaced by predetermined code expressions during compile time. Whenever replacement occurs, the term Macro Expansion is used to encapsulate this idea. The best way to demonstrate this, is by implementing the boolean operators as macros.
(define-syntax my-or (syntax-rules () [(my-or p q) ; Whenever Racket finds this syntax... (if p #t q)])) ; replace it with this. ; (my-or #t (/ 0 0)) ; macro gets expanded to... ; (if #t #t (/ 0 0)) ; this expression.
In this example,
my-or is implemented as a Racket macro. Whenever
my-or is used, it gets pattern-matched and replaced with the specified if-else statement which implements
my-and can be implemented in the same way.
(define-syntax my-and (syntax-rules () [(my-and p q) (if (not p) #f q)])) ; (my-and #f (/ 0 0)) ; macro gets expanded to... ; (if (not #f) #f (/ 0 0)) ; this expression.
Just like before,
my-and is a macro, and its implementation is an if-else statement that replaces any occurrence of
Since pattern-matching and macro expansion occurs during compile time, eager evaluation is prevented from taking place; this allows new implementations of boolean operators.
Eager evaluation prevents programs from passing arguments which yield errors, in the case of boolean expressions, the program cannot implement short-circuiting. To avoid this, macros can be used whenever our program needs to delay the execution of an expression, as illustrated by implementing the
or boolean operators.
Macros were useful for implementing boolean operators, but there is a downside to them. A function can be called 100 times and still occupy constant space. Macros on the other hand, can be pattern matched 100 times in code, and increase the size of the program 100 times the size of the macro. This is important when choosing whether or not an operation should be implemented as a macro or function.