Exercise

Alyssa P. Hacker doesn’t see why if needs to be provided as a special form. “Why can’t I just define it as an ordinary procedure in terms of cond?” she asks. Alyssa’s friend Eva Lu Ator claims this can indeed be done, and she defines a new version of if:

1
2
3
(define (new-if predicate then-clause else-clause)
  (cond (predicate then-clause)
        (else else-clause)))

Eva demonstrates the program for Alyssa:

1
2
3
4
5
(new-if (= 2 3) 0 5)
5

(new-if (= 1 1) 0 5)
0

Delighted, Alyssa uses new-if to rewrite the square-root program:

1
2
3
4
5
(define (sqrt-iter guess x)
  (new-if (good-enough? guess x)
          guess
          (sqrt-iter (improve guess x)
                     x)))

What happens when Alyssa attempts to use this to compute square roots? Explain.

Student Response

Looking at this code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
;code from previous sections
(define (square x) (* x x))

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

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

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

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

(define (sqrt x)
  (sqrt-iter 1.0 x))

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

(new-if (= 2 3) 0 5)

(new-if (= 1 1) 0 5)

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

When Alyssa attempts to use this to compute square roots, the program will never terminate. It doesn’t terminate because it is not if which is special form that evaluates the consequent or alternative expression, but new-if which is an ordinary procedure and will evaluate both the consequent and alternative expression.

Evaluation

Critique my response and provide any nuance or detail that I am overlooking. Explain in detail.

Your response is on the right track, but let’s add some important nuance to make the explanation more complete and precise:

  1. Applicative Order Evaluation: The key detail to emphasize is that Scheme uses applicative-order evaluation (evaluate the arguments first, then apply the procedure). This is crucial to understanding why new-if fails.

  2. Special Form vs. Regular Procedure: Your explanation about if being a special form is correct, but we can elaborate:

    • if is a special form that only evaluates the selected branch
    • new-if, as a regular procedure, evaluates ALL its arguments before executing the procedure body
    • This means both then-clause and else-clause are evaluated regardless of the predicate’s value
  3. Infinite Recursion Explanation: We can be more specific about why it never terminates:

    • In sqrt-iter, the else-clause contains a recursive call
    • Because new-if evaluates all arguments, it will always evaluate the recursive call
    • This happens before new-if even checks the predicate
    • This leads to infinite recursion regardless of whether the guess is good enough

Here’s a more detailed response:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
When Alyssa attempts to use this to compute square roots, the program enters an infinite recursion. This occurs because `new-if` is a regular procedure, not a special form like `if`. In Scheme's applicative-order evaluation:

1. All arguments to a procedure are evaluated before the procedure is applied
2. When `new-if` is called, it evaluates:
   - The predicate `(good-enough? guess x)`
   - The then-clause `guess`
   - The else-clause `(sqrt-iter (improve guess x) x)`

The crucial problem is that the else-clause (the recursive call) is always evaluated, even when the guess is good enough. This creates an infinite chain of recursive calls, as each `new-if` evaluation spawns another `sqrt-iter` call before it can even check if the current guess is good enough.

This is why `if` must be a special form - it needs to break the normal evaluation rules to conditionally evaluate only one of its branches based on the predicate's value.

This explanation better captures the underlying mechanism of why the code fails and helps readers understand the importance of special forms in the language.