82

I'm just beginning to work through SICP (on my own; this isn't for a class), and I've been struggling with Exercise 1.6 for a couple of days and I just can't seem to figure it out. This is the one where Alyssa re-defines if in terms of cond, like so:

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

She tests it successfully on some simple cases, and then uses it to re-write the square root program (which worked just fine with if):

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

The question then asks: "What happens when Alyssa attempts to use this to compute square roots? Explain." [If necessary, I'm happy to reproduce the other procedures (good-enough?, improve, etc.), just let me know.]

Now, I know what happens: it never returns a value, which means that the program recurses infinitely. I just can't explain why this happens. Whatever subtle difference exists between if and new-if is eluding me. Any and all help much appreciated.

3
  • 2
    The verb form of "recursive" is "to recurse", so it "recurses". Commented Jul 23, 2009 at 11:57
  • Compare also to 4.25: community.schemewiki.org/?sicp-ex-4.25
    – mihai
    Commented Aug 1, 2018 at 5:49
  • 1
    This is a good question. The book really doesn't adequately prepare the reader for this question. Most readers will be misled into thinking the issue is some difference between if and cond. In fact, if and cond behave identically. The issue is that when cond is packed into a function, the way that arguments are evaluated changes. As noted below in various answers, when a function is evaluated, its arguments are evaluated too, right away, by substitution. But it is impossible to evaluate the arguments to new-if, because sqr-iter simply calls itself repeatedly.
    – bhagerty
    Commented Nov 5, 2022 at 4:40

7 Answers 7

105

new-if is a function. When a function is called, what's the first thing that Scheme does with the argument list? It evaluates all the arguments.

2
  • 3
    OP wouldn't have this problem in Haskell! (He'd have a boatload of other problems ....)
    – davidbak
    Commented Sep 16, 2020 at 1:09
  • 1
    What really baked my noodle is realizing that change in behavior had nothing to do with switching from if to cond
    – andrew
    Commented Aug 29, 2021 at 23:43
60

new-if is a procedure, and Scheme uses applicative-order evaluation (1.1.5), so even before new-if is actually performed, it has to evaluate all the arguments first, which are guess and (sqrt-iter (improve guess x) x). You can see that the latter argument is a recursion, which calls a new new-if procedure, this is how the infinite loop occurs.

The ordinary if need not evaluate its arguments first, just go along the way, this is the difference between if and new-if. :)

1
  • 1
    The new-if procedure has three arguments: predicate, then-clause and else-clause. So when new-if is called, (good-enough? guess x), guess, and (sqrt-iter (improve guess x)) are evaluated. Is that right ? This doesn't change the outcome because only the evaluation of sqrt-iter causes the trouble. But imo you forgot one argument... Commented Apr 6, 2015 at 11:50
28

First of all you have to understand the difference between applicative order evaluation and normal order. Lisp uses applicative order, but conditional expressions are evaluated not like normal functions (sicp chapter 1.1.6):

(if <predicate> <consequent> <alternative>)

To evaluate an if expression, the interpreter starts by evaluating the <predicate> part of the expression. If the <predicate> evaluates to a true value, the interpreter then evaluates the <consequent> and returns its value. Otherwise it evaluates the <alternative> and returns its value.

1
16

There are three ways a form may be evaluated in Scheme:

  1. Applicative Order
    • evaluate the arguments and then apply
    • given f(x)=x+x: 3*f(1)*f(1)3*2*2
  2. Normal Order
    • fully expand and then reduce
    • given f(x)=x+x: 3*f(1)*f(1)3*(1+1)*(1+1) (also used in "lazy evaluation")
  3. Special Forms For example:
    • Booleans and and or. For example: (and <e1> ... <en>) evaluates Left → Right. If any evaluates to false, the value of the and expression is false, and the rest of the <e>'s are not evaluated.
    • Conditionals like if and cond
      • (if <predicate> <consequent> <alternative>): If the <predicate> evaluates to a true value, the interpreter then evaluates the <consequent> and returns its value. Otherwise it evaluates the <alternative> and returns its value
      • (cond (<p1> <e1>) ... (<pn> <en>)): The predicate <p1> is evaluated first. If its value is false, then <pn> is evaluated. If <pn>'s value is also false, then <pn+1> is evaluated. When true predicate, the interpreter returns the value of the corresponding consequent expression <e>.

In the case of Exercise 1.6:

  • new-if is a normal procedure. In Scheme (and many other languages), arguments are fully evaluated before the procedure is called. This is known as applicative order. ∴ sqrt-iter is called every time new-if is called, resulting in an infinite loop.
  • For the reader's edification, normal ifs are a special form. The recursive statement wouldn't be evaluated unless the <alternative> is called.
1
  • 1
    This was an excellent breakdown, and I thought you should know! Commented Sep 11, 2022 at 18:28
7

Previous answers are great. I'll add another one that explains in a more thorough way.

Another way to think of this difference is like this: How is the recursion using if stopping at some point and the one using new-if looping forever?

First lets see how these two ifs work in general and then how they work for this case.

if

This is explained by @alex-vasi:

To evaluate an if expression, the interpreter starts by evaluating the <predicate> part of the expression. If the <predicate> evaluates to a true value, the interpreter then evaluates the <consequent> and returns its value. Otherwise it evaluates the <alternative> and returns its value.

new-if

This is explained by @Schmudde:

All arguments are fully evaluated before the procedure is called.

How is the recursion using if stopping at some point?

It's stopping because at the point where the guess is good enough (ie (good-enough? guess x) is true), we will have:

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

And since the predicate is now true, the interpreter will evaluate the consequent (which is guess), return its value and will no longer evaluate the alternative (which is (sqrt-iter (improve guess x) x)).

So if actually evaluates (sqrt-iter (improve guess x) x) recursively up until the guess is good enough. Then it stops the recursion.

How is the recursion using new-if looping forever?

As with if, with new-if (sqrt-iter (improve guess x) x) will be evaluated recursively up until the guess is good enough.

But then it will keep evaluating (sqrt-iter (improve guess x) x) again and again. Why? Because when evaluating:

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

since new-if is a procedure, it will not check if (good-enough? guess x) is true or not in order to decide to evaluate either guess or (sqrt-iter (improve guess x)). What it will do is that it will evaluate (good-enough? guess x), guess and (sqrt-iter (improve guess x)) because those are the arguments of the procedure. So even when guess is good enough it will keep calling (sqrt-iter (improve guess x)) recursively :/.

1
  • 2
    This is like the part ii of the answer. Thanks!
    – wzso
    Commented Jan 19, 2021 at 8:29
1

Here is what I observed is:

new-if is a function, the interpeter uses applicative-order evaluation, which means all of the argument of the function will be evaluated first, one of the arguments of new-if is:

(sqrt-iter (improve guess x) x)

It's a recursion, the function calls itself, when this evaluated it will cause an infinite loop of recursion! Because when this function is evaluated it will call itself again, and this will happen each time it's evaluated.

It's like a rabbit hole, you get in and never get out.

0

Ex1.6. new-if:

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

Difference with ‘if-statements’: if-statements evaluate one by one from predicate -> consequent -> alternative,

however the ‘new-if’ has to evaluate all parameters aka arguments the MOMENT its called(which means 'else-clause' is evaluated at the start!!),

and thus this causes an infinite loop when any of these parameters call themselves into an iterative loop

Not the answer you're looking for? Browse other questions tagged or ask your own question.