4.54 Exercise 4.54

If we were going to add require as a special form (which, although not strictly necessary, would have some benefit – require is so ubiquitous that it makes sense to predefine it in the interpreter in some way), we would essentially have to evaluate the predicate and immediately call the relevant failure continuation if the predicate is not true. This is already evident in the partial source for analyze-require that is provided for this exercise:

(define (analyze-require exp)
  (let ((pproc (analyze (require-predicate exp))))
    (lambda (env succeed fail)
      (pproc env
             (lambda (pred-value fail2)
               (if ???
                   ???
                   (succeed 'ok fail2)))
             fail))))

We can already see that the predicate passed to if should be true if the predicate is false, because the succeed case is in the alternative position. The only thing that we need to be careful of is that we call the correct failure continuation if the predicate is false. This is relatively straightforward, though – we should call fail2, otherwise amb expressions in the predicate will not behave correctly. If we called the outermost fail continuation immediately, then an expression like (require (> 1 (amb 0 1 2 3))) would fully terminate without recognizing that some of the selectable values would pass the predicate. This is different from how the original require would behave, as that would continue to search for a solution (Note: strictly speaking, this implementation is already different than that of the procedure require, because the successful return value is ok instead of false.)

Here is the completed function:

(define (analyze-require exp)
  (let ((pproc (analyze (require-predicate exp))))
    (lambda (env succeed fail)
      (pproc env
             (lambda (pred-value fail2)
               (if (not pred-value)
                   (fail2)
                   (succeed 'ok fail2)))
             fail))))