Reputation: 101
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
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
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