Reputation: 63
I've been given two if-statements instructions in Racket:
(define (if-fun c thn els) (if c thn els))
(define-syntax-rule (if-mac c thn els) (if c thn els))
Would someone please mind explaining the differences between how these two if-statements are evaluated and provide an example using each if-statement definition? I'm having a hard time differentiating between how macros and function arguments are evaluated in this example. I've tried small examples such as:
(if-fun (> 3 4) true false) ;; #f
(if-mac (> 3 4) true false) ;; #f
But clearly that doesn't help me differentiate the two definitions.
-thanks
Upvotes: 5
Views: 1552
Reputation: 48745
(define (verbose arg)
(display arg) ; display
(newline) ; display newline
arg)) ; evaluate to arg
(if-fun (verbose (> 3 4))
(verbose 'true)
(verbose 'false))
; ==> false
This prints
#f
true
false
The macro version:
(if-mac (verbose (> 3 4))
(verbose 'true)
(verbose 'false))
; ==> false
It prints
#f
false
You see the difference? With a procedure every argument is evaluated and bound to the variables, then the body is evaluated.
In the macro version the code gets transformed, then evaluated. Thus the consequent expression was never executed since the predicate was #f
.
If you try making a recursive function:
(define (factorial n)
(if-fun (<= n 2)
n
(* n (factorial (- n 1)))))
(factorial 2)
Even when it hits the base case, since all 3 arguments are evaluated it will do (factorial 1), then (factorial 0), (factorial -1) ..... to negative infinity. It will never ever return a value but, sice it's not tail recursive, it will run out of memory.
(define (factorial n)
(if-mac (<= n 2)
n
(* n (factorial (- n 1)))))
(factorial 2)
When the procedure is evaluated the macro can be expanded so it turns into:
(define (factorial n)
(if (<= n 2)
n
(* n (factorial (- n 1)))))
And it would be as if you didn't use your macro at all. If you had a macro that printed something when it got expanded it would print once for every use in a procedure, before you use it.
It's like this because Scheme and Racket has eager evaluation. Eg. #!lazy
racket, which is a lazy version of #!racket
, if
and other special forms are made as procedures since evaluation is by need. There are no need for macros in a lazy language.
Upvotes: 0
Reputation: 16260
From your comment it sounds like you already figured this out. The key question is, when (if ever) do things get evaluated?
Another key point is that functions and macros are totally different, even though their definition and use can look the same.
The way you use a function and a macro appears exactly the same: (thing other stuff)
. It's not apparent whether thing
is a function or a macro. This is both good and bad. Mostly it's very good.
As for defining things, the way you define a macro using define-syntax-rule
is extremely similar to how you define
a function. This is both good and bad. I'd say it's mostly quite bad, when you're first learning -- because it makes it really easy to forget how completely different macros are from functions. It can be confusing!
When you call a function, at run time all of the arguments are evaluated, then given to the function. That's why an argument to if-fun
like (/ 1 0)
will cause an error. It is evaluated (and elicits a divide by zero error) before control even gets inside if-fun
.
(Sidenote: When you call a function with lazy evaluation, or with manual "thunks", then the evaluation is delayed. An if-lazy
is able to call either the thn
or els
procedure arguments, only as/when needed. When the condition is false, it doesn't even attempt to call els
.)
When you invoke a macro:
When: The macro does its work before your program even runs. The macro works at compile time, not later at run time.
What: A macro transforms pieces of code into other pieces of code. But the code isn't evaluated, yet. The code is evaluated only later, at run time. So the "arguments" to if-mac
aren't evaluated by the macro. They just get plugged into the code for the real if
form, which is a macro (or primitive special form) that evaluates only what is required.
The final confusing part is that, because your example's then and else expressions don't have any side effects and don't cause any error, the difference isn't apparent.
Upvotes: 5