Skip to main content
GameDev.net gamedev.net
🔒 Locked

Scheme question

Started by googlyeyes Dec 19, 2004 at 1:42 PM 7 replies 2.2k views
Original Post
googlyeyes
googlyeyes
I've started reading through Structure and Interpretation of Computer Programs but I'm stuck on exersise 1.6. the code works perfectly until you use new-if instead of if

(define (new-if predicate then-clause else-clause)
   (cond (predicate then-clause)
   (else else-clause)))

(define (good-enough? guess x)
   (< (abs (- (square guess) x)) 0.001))

(define (improve guess x)
   (average guess (/ x guess)))

(define (average x y)
   (/ (+ x y) 2))

(define (sqrt-iter guess x)
   (new-if (good-enough? guess x)
        guess
        (sqrt-iter (improve guess x) 
                       x)))

(define (sqrt x)
    (sqrt-iter 1.0 x))
(sqrt 1) returns aborting! maximum recursion depth exeeded. so why does it recurse forever when it worked fine before. EDIT: A simpler case of the same problem is (define (new-if predicate then-clause else-clause) (cond (predicate then-clause) (else else-clause))) (define (test a b) (new-if (> a b) 1 (test a (+ b 1)))) (test 3 7) => aborting! maximum recursion depth exeeded.
Trap
Trap
In Common Lisp arguments to functions are evaluated before calling the function. If scheme does the same it will recurse infinitely before even calling new-if
googlyeyes
googlyeyes
Ok, but why does it work with normal if?
Trap
Trap
if isn't a function.
C-Junkie
C-Junkie
Quote:
Original post by googlyeyes
Ok, but why does it work with normal if?

applicative VS normal order evaluation.

Here's a very simpe example.
(define (div a b)  (if (> a b)     (/ b a)     (/ a b)))(div 0 4)


0 not > 4, so return 0/4. Right?

Replace with new-if. Become enlightened.
googlyeyes
googlyeyes
Ok I think I got it. so when I have:

(define (sqrt-iter guess x)
(new-if (good-enough? guess x)
guess
(sqrt-iter (improve guess x)
x)))

if, which uses normal order evaluation, will only calculate sqrt-iter again if good-enough is false,
but new-if calculates it no matter what because with applicative order evaluation it calculates the innermost stuff first.

thanks for the help :)
Zahlman
Zahlman
Yep. If I'm thinking clearly, you should be able to use a lambda to explicitly delay the evaluation, like this:

(define (new-if predicate then-clause else-clause)
(cond (predicate then-clause) ; we *do* want to evaluate 'predicate'
(else (lambda () (else-clause)))))
flangazor
flangazor
Bigloo crashes on the original code. :)

The previous posters were correct that it is evaluating the arguments to the new-if function before calling new-if. This is obviously calling the recursion since one of the arguments to new-if is sqrt-iter. Here's a trace in MzScheme:
> (trace new-if sqrt-iter good-enough? improve new-sqrt average)(new-if sqrt-iter good-enough? improve new-sqrt average)> (new-sqrt 1)|(new-sqrt 1)| (sqrt-iter 1.0 1)| |(good-enough? 1.0 1)| |#t| |(improve 1.0 1)| | (average 1.0 1.0)| | 1.0| |1.0| |(sqrt-iter 1.0 1)| | (good-enough? 1.0 1)| | #t| | (improve 1.0 1)| | |(average 1.0 1.0)| | |1.0| | 1.0| | (sqrt-iter 1.0 1)| | |(good-enough? 1.0 1)| | |#t| | |(improve 1.0 1)| | | (average 1.0 1.0)| | | 1.0| | |1.0| | |(sqrt-iter 1.0 1)| | | (good-enough? 1.0 1)| | | #t| | | (improve 1.0 1)| | | |(average 1.0 1.0)| | | |1.0| | | 1.0| | | (sqrt-iter 1.0 1)| | | |(good-enough? 1.0 1)| | | |#t| | | |(improve 1.0 1)| | | | (average 1.0 1.0)| | | | 1.0| | | |1.0| | | |(sqrt-iter 1.0 1)| | | | (good-enough? 1.0 1)| | | | #t| | | | (improve 1.0 1)| | | | |(average 1.0 1.0)| | | | |1.0| | | | 1.0| | | | (sqrt-iter 1.0 1)| | | | |(good-enough? 1.0 1)| | | | |#t| | | | |(improve 1.0 1)| | | | | (average 1.0 1.0)| | | | | 1.0| | | | |1.0| | | | |(sqrt-iter 1.0 1)| | | | | (good-enough? 1.0 1)| | | | | #t| | | | | (improve 1.0 1)| | | |[10](average 1.0 1.0)| | | |[10]1.0| | | | | 1.0| | | | | (sqrt-iter 1.0 1)| | | |[10](good-enough? 1.0 1)| | | |[10]#t| | | |[10](improve 1.0 1)| | | |[11](average 1.0 1.0)

So how do you redefine these functions? You need to define syntax, not functions. Syntax definitions don't evaluate arguments until they have to. This makes them extremely powerful. You can do things like -well, you can redefine 'if' for a start. :)

(define-syntax new-if  (syntax-rules ()    ((new-if pred then el)     (cond ((pred) then)	    (else el)))))


What is going on here is that we have our declaration that we are defining syntax, define-syntax. It's called new-if. This syntax has a single rule which will be defined by syntax-rules. Syntax rules takes two arguments. The first is a set of identifiers that you won't use by value. These are your syntactic sugar tokens. The next argument is your syntax rule. This is made up of a pattern and a template.

The pattern is where the syntactic sugar comes in. You define the variables being passed in and define how it is to be decorated. It may be more obvious with an example (same as above apart from the pattern and identifier sugar):
(define-syntax new-if  (syntax-rules (then else)    ((new-if <pred> then <then-case> else <else-case>)     (cond (<pred> <then-case>)	    (else <else-case>)))))

We can see here that we tell the rule that 'then' and 'else' are part of the sugar that make up the pattern. The rest of what is passed in is an identifier that you can play with. Keep in mind that if you are testing this out, then you need to change sqrt-iter to:
(define (sqrt-iter guess x)  (new-if (good-enough? guess x)	  then guess	  else (sqrt-iter (improve guess x) 			  x)))


After the pattern is the template comes the template. It's pretty much like a function definition so I won't bother explaining it. However, you should know that you cannot bind variables in the syntax definition since that would be unhygenic. A discussion on this is here.

This may be helpful as well.

If you have any more questions or need clarification, don't hesitate to ask.

Side Note: I see people using SGML/XML style tokens when they are defining syntax that uses sugar. You don't have to use it, and in fact it makes discussing it on gamedev annoying since you have to use < and > on everything. It *does* make it easier to read the intention of patterns, though. I've done some playing and I think it would be good to prefix tokens used for templates with $. What do you lot think?

[Edited by - flangazor on December 20, 2004 4:07:02 PM]

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.