0

Question: Consider a metacircular evaluator which does not implement the if construct, but only a cond. Louis Reasoner claims that if is unnecessary, since we could evaluate the following procedure using the metacircular evaluator:

(define (if predicate action alternative)
  (cond (predicate action)
        (else alternative)))

Explain why this does not work. In particular, suppose you use this if procedure to define factorial:

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

The result of evaluating (factorial 3) using the if statement above makes the program running forever, but I have no idea why. Any hint on this will be appreciated! Thanks!

yzhan
  • 170
  • 1
  • 3
  • 13
  • I printed the n in the factorial loops, and n is decreasing from 3 to negative infinity. Even though n already equals to 0 after a couple loops, the program is still not stopping. – yzhan Apr 17 '17 at 03:08

2 Answers2

2

An if expression is a special form, it doesn't follow the same evaluation rules as a normal procedure - hence it can't be implemented by a procedure, it has to use a syntactical transformation (say, using a macro or in your case, changing the underlying interpreter). To see this, look at this simple example:

(if #t 'ok (/ 1 0))
=> 'ok

The division by zero never occurred, because only the 'ok part of the expression got evaluated. Try to evaluate the same expression using your implementation of if - you'll get a division by zero error.

So now you see, an if expression is evaluated differently depending on the value of the condition, only the consequent is executed or only the alternative is executed, but never both. And that's the reason why your factorial example fails - even if the base case is reached, the other branch is also executed, creating an infinite loop.

Óscar López
  • 232,561
  • 37
  • 312
  • 386
1

Note that if/cond are syntax, not procedures. There is a difference in using the if syntax, and actually defining it and trying to use it. To illustrate, consider the following:

(+ 1 (- 4 2))

Here we have the + procedure, that takes zero or more numbers and returns their sum. We have applied the procedure with two arguments, the second being an expression (- 4 2) instead of a value. Notice that this will not result in a contract violation error, and actually evaluate to 3. Why? Because any argument which is not a value is first simplified to a value. So the expression evaluates as:

(+ 1 (- 4 2))
=> (+ 1 2)
=> 3

Coming back to your defined if procedure, the same rules apply, in that any arguments passed to the function, that are not values themselves, will be reduced to values before using the if procedure. So the evaluation for (factorial 1) becomes:

(factorial 1)
=> (if (= 1 1) 1 (* 1 (factorial 0)))
=> (if #t 1 (* 1 (factorial 0)))
=> (if #t 1 (* 1 (if (= 0 1) 1 (* 0 (factorial -1)))))
=> (if #t 1 (* 1 (if #f 1 (* 0 (factorial -1)))))
=> ... (endless loop to negative infinity)

This is different from using the if syntax, where evaluation is short circuited, meaning that the else case is only evaluated if the predicate is false. For example,

(if #t 1 (+ 1 'a))

will not throw an error for trying to add a number and a symbol, because it will not evaluate the else-expression in the first place (since predicate evaluates to true). Hence, it returns 1.

assefamaru
  • 2,751
  • 2
  • 10
  • 14