ralphxiaoz
ralphxiaoz

Reputation: 101

define if with cond doesn't work

I try to implement a "special-if" that suppose to behave like regular "if" with cond. Here's the code:

(define (special-if pre act alt)
  (cond (pre act)
        (else alt)))

To test if this works, I wrote a factorial function with this "special-if":

(define (factorial n)
       (special-if (= n 1)
           1
           (* n (factorial (- n 1)))))

However, when I evaluate (factorial 3), it runs forever. Seems the predicate part (= n 1) was never evaluated. Can anyone tell me why this won't work? Thanks.

Upvotes: 1

Views: 494

Answers (2)

Throw Away Account
Throw Away Account

Reputation: 2671

You've heard why your special-if doesn't work, but you accepted an answer that doesn't tell you how to make it work:

(define-syntax special-if 
  (syntax-rules ()
      ((_ pre act alt)
       (cond (pre act)
             (else alt)))))

That defines a macro, which is expanded at compile time. Every time the compiler sees something that matches this pattern and begins with special-if, it gets replaced with the template shown. As a result, pre, act, and alt don't get evaluated until your special-if form gets turned into a cond form.

Understanding how macros work is difficult using Scheme, because Scheme does its damnedest to hide what's really going on. If you were using Common Lisp, the macro would be a simple function that takes snippets of uncompiled code (in the form of lists, symbols, strings, and numbers) as arguments, and returns uncompiled code as its value. In Common Lisp, special-if would just return a list:

(defmacro special-if (pre act alt)
   (list 'cond (list pre act)
               (list t alt)))

Scheme macros work the same way, except the lists, symbols, etc. are wrapped in "syntax objects" that also contain lexical information, and instead of using list operators such as car and cdr, Scheme and Racket provide a pattern-matching and template engine that delves into the syntax objects and handles the extra data (but doesn't allow you to handle any of it directly).

Upvotes: 2

Óscar López
Óscar López

Reputation: 236140

Your special if is doomed to fail, I'm afraid. Remember: if is an special form with different evaluation rules (and cond is a macro implemented using if), that's why an expression like this runs fine even though it has a division by zero:

(if true 'ok (/ 1 0))
=> 'ok

... whereas your special-if will raise an error, because it's using the normal evaluation rules that apply for procedures, meaning that all its arguments get evaluated before executing the procedure:

(special-if true 'ok (/ 1 0))
=> /: division by zero

Now you see why your code fails: at the bottom of the recursion when n is 1 both the consequent and the alternative will execute!

(special-if (= 1 1)
    1                          ; this expression is evaluated
    (* 1 (factorial (- 1 1)))) ; and this expression too!

... And factorial will happily continue to execute with values 0, -1, -2 and so on, leading to an infinite loop. Bottom line: you have to use an existing special form (or define a new macro) for implementing conditional behavior, a user-defined standard procedure simply won't work here.

Upvotes: 4

Related Questions