sicp-ex-4.5



<< Previous exercise (4.4) | Index | Next exercise (4.6) >>


meteorgan

  
  
 (define (extended-cond-syntax? clause) (eq? (cadr clause) '=>)) 
 (define (extended-cond-test clause) (car clause)) 
 (define (extended-cond-recipient clause) (caddr clause)) 
 (define (cond->if expr) 
         (expand-clauses (cond-clauses expr))) 
 ;; convert cond expression to if expression 
 (define (expand-clauses clauses) 
         (if (null? clauses) 
                 'false 
                 (let ((first (car clauses)) 
                           (rest (cdr clauses))) 
                         (cond ((cond-else-clause? first) 
                                    (if (null? rest) 
                                            (sequence->exp (cond-actions first)) 
                                            (error "ELSE clause isn't last -- COND->IF" clauses))) 
                                   ((extended-cond-syntax? first) 
                                    (make-if (extended-cond-test first) 
                                                         (list (extended-cond-recipient first) 
                                                                   (extended-cond-test first)) 
                                                         (expand-clauses rest))) 
                                 (else  
                                         (make-if (cond-predicate first) 
                                                      (sequence->exp (cond-actions first)) 
                                                      (expand-clauses rest))))))) 

bagratte

  
  
  
 (define (expand-clauses clauses) 
   (if (null? clauses) 
       'false                          ; no else clause 
       (let ((first (car clauses)) 
             (rest (cdr clauses))) 
         (if (cond-else-clause? first) 
             (if (null? rest) 
                 (sequence->exp (cond-actions first)) 
                 (error "ELSE clause isn't last -- COND->IF" 
                        clauses)) 
             (let ((test (cond-predicate first)) 
                   (recepient (if (eq? (car (cond-actions first)) '=>) 
                                  (cadr (cond-actions first)) 
                                  false))) 
               (make-if test 
                        (if recepient 
                            (list recepient test) ;test-recepient cond 
                            (sequence->exp (cond-actions first))) ;normal cond 
                        (expand-clauses rest))))))) 

leafac

Both solutions above fail to consider that evaluating the condition twice (once to check for truthiness, then again to pass the value as parameter to the lambda) may have undesired consequences on the presence of side-effects. Suppose the clause is

 (begin (set! x (+ x 1)) x) 

for example.

The solution is to use lambdas that are immediately applied to encode `let' bindings:

 (define (expand-clauses clauses) 
   (if (null? clauses) 
       'false                          ; no else clause 
       (let ((first (car clauses)) 
             (rest (cdr clauses))) 
         (if (cond-else-clause? first) 
             (if (null? rest) 
                 (sequence->exp (cond-actions first)) 
                 (error "ELSE clause isn't last -- COND->IF" 
                        clauses)) 
             (if (extended-cond? first) 
                 (make-application (make-lambda '(_cond-parameter) 
                                                (make-if _cond-parameter 
                                                         (make-application (extended-cond-actions first) 
                                                                           _cond-parameter) 
                                                         (expand-clauses rest))) 
                                   (cond-predicate first)) 
                 (make-if (cond-predicate first) 
                          (sequence->exp (cond-actions first)) 
                          (expand-clauses rest))))))) 
  
 (define (extended-cond? clause) 
   (eq? (cadr (cond-actions clause)) '=>)) 
  
 (define (make-application function parameters) 
   (cons function parameters)) 
  
 (define (extended-cond-actions clause) 
   (caddr clause)) 

karthikk

leafac is absolutely right. (Note let does't solve the problem here because it doesn't evaluate the predicate in the appropriate environment ...as far as it is concerned since all that cond is is a syntactic transformation, its arguments are data not code. What leafac is essentially doing is implementing that let in the transformed data which will then in another pass through eval be evaluated as code) However if we do allow ourselves the ability to evaluate the predicate twice, (and its weird to put in side effects in a test predicate in any case), then there is a really simple solution:

 (define (special-cond? clause) 
   (eq? (cadr clause) '=>)) 
  
 (define (cond-actions clause) 
   (if (special-cond? clause) 
       (cons (caddr clause) (cond-predicate clause)) 
       (cdr clause)))