Scheme: difference between define and define-syntax-rule

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

Answers (2)

Sylwester
Sylwester

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

Greg Hendershott
Greg Hendershott

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:

    1. When: The macro does its work before your program even runs. The macro works at compile time, not later at run time.

    2. 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

Related Questions