To understand this solution, just we need to check that
make-let needs both arguments to be list. Thus the result of each iteration of
fold-right must be list.
fold-right returns list even in the last iteration, we just take the first element.
1 2 3 4 5 6 7 8 9 10 11 12 ;; var-defs and body should be list (define (make-let var-defs body) (cons 'let (cons var-defs body))) (define (let*? exp) (tagged-list? exp 'let*)) (define (let*->let exp) (car (fold-right (lambda(new rem) (list (make-let (list new) rem))) (let-body exp) (let-vardefs exp))))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ;;; M-Eval input: (let* ((x (+ 1 2)) (y (+ x 100))) (+ x y)) ;;; M-Eval value: 106 ;;; M-Eval input: (let* ((x (+ 1 2)) (y (+ x 100)) (z y)) (+ x y z)) ;;; M-Eval value: 209 ;;; M-Eval input: (let* ((x (+ 1 2)) (y (+ x 100)) (z y)) (define m 1000) (+ x y z m)) ;;; M-Eval value: 1209
Do we need to expand explicitly?
This is more interesting part because it feels like magical that somehow every thing is working as per theory.
No, we need not to expand let explicitly. This is possible because of the way we lookup variables in environment.
It is important to understand this conceptually. Here is my understanding:
- let* creates let
- let creates lambda
- when lambda is evaluated a procedure is created. See
- Now after creation of procedure, we
applythis procedure to its arguments.
- During application we extend the base environment and add the procedure parameters in the extended environment.
- If there is new lambda expression/procedure, same iteration is done.
- Thus every nested lambda get the required variables!