Some students have asked for more examples like problems 2, 3, and 4 on homework 5. Since the calculations were cancelled in that problem, I have felt free to give a more methodical, and better thought out form of the calculations below. 1. A very simple example (let ((x 227)) x) (i) since there is only one let, there is only one evironment to be drawn. (I'm drawing these with squiggly arrows, to make the analogy with parts (ii) and (iii) better.) Environment 1 (parent Envirioment 0): x ~~~> [227] (ii) and (iii) the x in the body of the LET should have a smooth arrow drawn from it to the first x (the formal parameter. The formal parameter has a squiggly arrow drawn to the expression that will produce its value. (let ((x ~~~> 227)) ^ ----| x) (iv) We calculate the value as follows. Environment 0: + ~~~> [procedure | ...| Env0] ... (let ((x 227)) x) To evaluate the LET we need to compute the value of 227 ==> 227 Then evaluate the body in the following environment Environment 1 (parent Environment 0): x ~~~> [227] x ==> 227 Note that a calculation in this format is a series of environments and expressions, the expressions following an environment are evaluated in that environment. Each let produces a new environment, in which we evaluate the body. 2. Now a more complex example, but still simpler than the one given in the homework. (let ((number 57)) (let ((rem3 (remainder number 3)) (rem5 (remainder number 5)) (rem7 (remainder number 7))) (let ((sum (+ (* rem3 70) (* rem5 21) (* rem7 15)))) (remainder sum 105)))) (i) There are 3 LETs so we need to decribe 3 environments... Environment 1 (parent Environment 0): number ~~~> [57] Environment 2 (parent Environment 1): rem3 ~~~> [0] rem5 ~~~> [2] rem7 ~~~> [1] Environment 3 (parent Environment 2): sum ~~~> [57] (ii) and (iii) /---------------------------------\ v ^ | (let ((number ~~~> 57)) | | |--------------\ | | | v | | | (let ((rem3 ~~~> (remainder number 3)) | | |-------------\ /-------->| | | v | | | | (rem5 ~~~> (remainder number 5)) | | | |-----------\ /-------->/ | | | v | | | | (rem7 ~~~> (remainder number 7))) \-|-|------------------------------\ | | | | | (let ((sum ~~~~> (+ (* rem3 70) | | ^ \-|-----------<-|-<--------------\ | | | | | | | | (* rem5 21) \-------------|----------------\ | | \-----\ (* rem7 15)))) | | (remainder sum 105)))) (iv) The value is calculated as follows. Environment 0: + ~~~> [procedure | ...| Env0] ... (let ((number 57)) (let ((rem3 (remainder number 3)) (rem5 (remainder number 5)) (rem7 (remainder number 7))) (let ((sum (+ (* rem3 70) (* rem5 21) (* rem7 15)))) (remainder sum 105)))) To evaluate the LET we need to compute the value of 57 ==> 57 Then evaluate the body in the following environment Environment 1 (parent Environment 0): number ~~~> [57] (let ((rem3 (remainder number 3)) (rem5 (remainder number 5)) (rem7 (remainder number 7))) (let ((sum (+ (* rem3 70) (* rem5 21) (* rem7 15)))) (remainder sum 105))) To evaluate the LET we need to compute the values of (remainder number 3) = (remainder 57 3) ==> 0 (remainder number 5) = (remainder 57 5) ==> 2 (remainder number 7) = (remainder 57 7) ==> 1 Then evaluate the body in the following environment Environment 2 (parent Environment 1): rem3 ~~~> [0] rem5 ~~~> [2] rem7 ~~~> [1] (let ((sum (+ (* rem3 70) (* rem5 21) (* rem7 15)))) (remainder sum 105)) To evaluate the LET we need to compute the value of (+ (* rem3 70) (* rem5 21) (* rem7 15)))) = (+ (* 0 70) (* 2 21) (* 1 15)))) = (+ 0 42 15) ==> 57 Then evaluate the body in the following environment Environment 3 (parent Environment 2): sum ~~~> [57] (remainder sum 105) = (remainder 57 105) ==> 57 3. Here's a simple procedure application. (let ((two 2)) (let ((add2 (lambda (x) (+ x two)))) (add2 3))) (i) Env1 (parent Env0): two ~~> [2] Env2 (parent Env1): add2 ~~> [procedure|(x)|(+ x two)|Env1] (ii and iii) (let ((two ~~> 2)) ~~~~~~~~~~~~~~~~~~~ ^ ~ ~ \---------------~--------\ ~ ~ | ~ (let ((add2~~>(lambda (x) (+ x two)))) ~ ^ ^ | ~ / |-----/ ~ / ~ (add2 3))) ~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (iv) Env0: + ~~~> [procedure | ...| Env0] ... (let ((two 2)) (let ((add2 (lambda (x) (+ x two)))) (add2 3))) To evaluate the LET we need to compute the value of 2 ==> 2 Then evaluate the body in the following environment Env1 (parent Env0): two ~~> [2] (let ((add2 (lambda (x) (+ x two)))) (add2 3)) To evaluate the LET we need to compute the value of (lambda (x) (+ x two)) ==> [procedure|(x)|(+ x two)|Env1] Then evaluate the body in the following environment Env2 (parent Env1): add2 ~~> [procedure|(x)|(+ x two)|Env1] (add2 3) To evaluate the application we first evaluate the operator and its arguments add2 ==> [procedure|(x)|(+ x two)|Env1] 3 ==> 3 then we evaluate the body (of the closure for add2) in Env3 (parent Env1): ;; parent from the closure for add2 x ~~> [3] (+ x two) To evaluate the application we first evaluate the operator and its arguments + ==> [procedure| ... |Env0] x ==> 3 two ==> 2 ==> 5 4. This was a homework problem. (let ((kmize (lambda (y) (let ((num (car y)) (unit (cadr y))) (let ((conversion-factor (cond ((eq? unit 'miles) 1.609344) ((eq? unit 'feet) 3.048e4) ((eq? unit 'inches) 2.54e5)))) (list (* conversion-factor num) 'kilometers)))))) (let ((unit (list 5 'miles))) (list unit 'is (kmize unit)))) (i) Env1 (parent Env0): kmize ~~> [procedure |(y) |(lambda (y) (let ((num (car y)) (unit (cadr y))) (let ((conversion-factor (cond ((eq? unit 'miles) 1.609344) ((eq? unit 'feet) 3.048e4) ((eq? unit 'inches) 2.54e5)))) (list (* conversion-factor num) 'kilometers)))) |Env0] Env2 (parent Env1): unit ~~> [(5 miles)] (ii) and (iii) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~ ~ ~ (let ((kmize (lambda (y) ~ ^ ^--\ ~ /---------/ \--------\-<----\ ~ | (let ((num ~~> (car y)) | ~ | ^ | ~ | /------/ /-----/ ~ | | (unit ~~>(cadr y))) ~ | | ^ ~ | | \----------------------------------\ ~ | | | ~ | | (let ((conversion-factor | ~ | | ^ | ~ | | /--------/ ^ ~ | | | (cond /----------------------->-| ~ | | | / | ~ | | | ((eq? unit 'miles) 1.609344) ^ ~ | | | | ~ | | | /------------------------>-| ~ | | | ((eq? unit 'feet) 3.048e4) | ~ | | | ^ ~ | | | /------------------------>-/ ~ | | | ((eq? unit 'inches) 2.54e5)))) ~ | | | ~ | | \-------------- ~ | | \ ~ | | (list (* conversion-factor num) 'kilometers)))))) ~ | \--------------------------------/ ~ | ~ | (let ((unit ~~~> (list 5 'miles))) ~ | ^ ~ | \ ~ | (list unit 'is (kmize unit)))) ~ | / ^ ~ \--------------------------- ~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (iv) Env0: cons ~~> [primitive-procedure|(x l)|...|Env0] ... (let ((kmize (lambda (y) (let ((num (car y)) (unit (cadr y))) (let ((conversion-factor (cond ((eq? unit 'miles) 1.609344) ((eq? unit 'feet) 3.048e4) ((eq? unit 'inches) 2.54e5)))) (list (* conversion-factor num) 'kilometers)))))) (let ((unit (list 5 'miles))) (list unit 'is (kmize unit)))) To evaluate the LET we need to compute the value of (lambda (y) (let ((num (car y)) (unit (cadr y))) (let ((conversion-factor (cond ((eq? unit 'miles) 1.609344) ((eq? unit 'feet) 3.048e4) ((eq? unit 'inches) 2.54e5)))) (list (* conversion-factor num) 'kilometers)))))) ==> [procedure |(y) |(lambda (y) (let ((num (car y)) (unit (cadr y))) (let ((conversion-factor (cond ((eq? unit 'miles) 1.609344) ((eq? unit 'feet) 3.048e4) ((eq? unit 'inches) 2.54e5)))) (list (* conversion-factor num) 'kilometers)))) |Env0] Then evaluate the body in the following environment Env1 (parent Env0): kmize ~~> [procedure |(y) |(lambda (y) (let ((num (car y)) (unit (cadr y))) (let ((conversion-factor (cond ((eq? unit 'miles) 1.609344) ((eq? unit 'feet) 3.048e4) ((eq? unit 'inches) 2.54e5)))) (list (* conversion-factor num) 'kilometers)))) |Env0] (let ((unit (list 5 'miles))) (list unit 'is (kmize unit))) To evaluate the LET we need to compute the value of (list 5 'miles) To evaluate this application we eval the operator and the operands list ==> [primitive-procedure|args|...|Env0] 5 ==> 5 'miles ==> miles ==> (5 miles) Then we evaluate the body in the following environment Env2 (parent Env1): unit ~~> [(5 miles)] (list unit 'is (kmize unit)) To evaluate this application we eval the operator and the operands list ==> [primitive-procedure|args|...|Env0] unit ==> (5 miles) 'is ==> is (kmize unit) To evaluate this application we eval the operator and the operands kmize ==> [procedure |(y) |(lambda (y) (let ((num (car y)) (unit (cadr y))) (let ((conversion-factor (cond ((eq? unit 'miles) 1.609344) ((eq? unit 'feet) 3.048e4) ((eq? unit 'inches) 2.54e5)))) (list (* conversion-factor num) 'kilometers))) |Env0] unit ==> (5 miles) Then we evaluate the body (of the closure for kmize) in Env3 (parent Env0): ;; parent from closure y ~~> [(5 miles)] (let ((num (car y)) (unit (cadr y))) (let ((conversion-factor (cond ((eq? unit 'miles) 1.609344) ((eq? unit 'feet) 3.048e4) ((eq? unit 'inches) 2.54e5)))) (list (* conversion-factor num) 'kilometers))) To evaluate the LET we need to evaluate (car y) ==> 5 (cadr y) ==> miles Then we eval the body in Env4 (parent Env3): num ~~> [5] unit ~~> [miles] (let ((conversion-factor (cond ((eq? unit 'miles) 1.609344) ((eq? unit 'feet) 3.048e4) ((eq? unit 'inches) 2.54e5)))) (list (* conversion-factor num) 'kilometers)) To evaluate the LET we need to evaluate (cond ((eq? unit 'miles) 1.609344) ((eq? unit 'feet) 3.048e4) ((eq? unit 'inches) 2.54e5)))) ==> 1.609344 Then we eval the body in Env5 (parent Env4): conversion-factor ~~> [1.609344] (list (* conversion-factor num) 'kilometers)) To evaluate this application we first do the operator and operands list ==> [primitive-procedure|args|...|Env0] (* conversion-factor num) ==> 8.04672 'kilometers ==> kilometers ==> (8.04672 kilometers) ==> ((5 miles) is (8.04672 kilometers)) 5. Here's a really wierd one. See p.156 of the Little LISPer... (let ((Y (lambda (M) (let ((future (lambda (future) (M (lambda (arg) ((future future) arg)))))) (M (lambda (arg) ((future future) arg))))))) (let ((length (Y (lambda (recur) (lambda (l) (if (null? l) 0 (add1 (recur (cdr l))))))))) (length '(a)))) (i) I'm abbreviating the environment names as we did in class below. Env1 (parent Env0): Y ~~~> [procedure | (M) | (let ((future ... )) (M ...)) | Env0] Env5 (parent Env1): length ~~~> [procedure | (recur) | (Y (lambda (l) (if ...)))| Env1] see below for the real value of length... (ii) and (iii) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>~~ (let ((Y ~~> (lambda (M) ~ ^ /--------^ ~ | | v--------------------------------\ v | |(let ((future | ~ | | ~ | ~ | | ~ | ~ | | ~~~~~~~~~~~~~~ | ~ | | ~ ~ ~ | ~ | | ~ v ~ | ~ | | ~ (lambda (future) | ~ | \--<--~----\ ^------<---\ | ~ | \ ~ | / | ~ | | ~ (M (lambda (arg) / | ~ | | ~ ^----/----\ | ~ | | ~ -->---/ | | ~ | | ~ / / | | ~ | | ~ ((future future) arg)))))) | ~ | | ~ | ~ | | v /-------------------/ ~ | (M (lambda (arg) / ~ | ^-----/---\ ~ | -------/ | ~ | / / | ~ | ((future future) arg))))))) ~ | ~ \-------------\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<~~ | v (let ((length ~~> (Y (lambda (recur) /---^ ^------------\ | ~~~~~~~~~~~~~~~~~~~~~ \ | ~ ~ \ | ~ (lambda (l) \ | ~ ^---\ \ | ~ | | | ~ (if (null? l) 0 (add1 (recur (cdr l))))))))) | ~ | v (length '(a)))) (iv) This is way more complicated than a human should do, but it does illustrate everything about let Env0: + ~~~> [procedure | ...| Env0] ... (let ((Y (lambda (M) (let ((future (lambda (future) (M (lambda (arg) ((future future) arg)))))) (M (lambda (arg) ((future future) arg))))))) (let ((length (Y (lambda (recur) (lambda (l) (if (null? l) 0 (add1 (recur (cdr l))))))))) (length '(a)))) To evaluate the LET we need to compute the value of (lambda (M) (let ((future (lambda (future) (M (lambda (arg) ((future future) arg)))))) (M (lambda (arg) ((future future) arg))))) ==> [procedure | (M) | (let ((future (lambda (future) (M (lambda (arg) ((future future) arg)))))) (M (lambda (arg) ((future future) arg)))) | Env0] Then evaluate the body in the following environment Env1 (parent Env0): Y ~~~> [procedure|(M)|(let ((future ... )) (M ...))|Env0] (let ((length (Y (lambda (recur) (lambda (l) (if (null? l) 0 (add1 (recur (cdr l))))))))) (length '(a)))) To evaluate the LET we need to compute the value of (Y (lambda (recur) (lambda (l) (if (null? l) 0 (add1 (recur (cdr l))))))) To evaluate the application we first evaluate the operator and its arguments Y ==> [procedure|(M)|(let ((future ... )) (M ...))|Env0] (lambda (recur) (lambda (l) (if (null? l) 0 (add1 (recur (cdr l)))))) ==> [procedure|(recur)|(lambda (l)...)|Env1] then we evaluate the body (of Y) in the following Env2 (parent Env0): ;; from the closure for Y M ~~> [procedure|(recur)|(lambda (l) (if (null? l) 0 (add1 (recur (cdr l)))))) |Env1] (let ((future (lambda (future) (M (lambda (arg) ((future future) arg)))))) (M (lambda (arg) ((future future) arg)))) To evaluate the LET we need to compute the value of (lambda (future) (M (lambda (arg) ((future future) arg)))) ==> [procedure|(future)|(M (lambda (arg) ((future future) arg))) |Env2] Then evaluate the body in the following environment Env3 (parent Env2): future ~~> [procedure|(future)|(M (lambda (arg) ((future future) arg))) |Env2] (M (lambda (arg) ((future future) arg))) To evaluate the application we first evaluate the operator and its arguments M ==> [procedure|(recur)|(lambda (l) (if (null? l) 0 (add1 (recur (cdr l))))) |Env1] [procedure|(recur)|(lambda (l)...)|Env1] (lambda (arg) ((future future) arg)) ==> [procedure|(arg)|(lambda (arg) ((future future) arg)) |Env3] Then evaluate the body (of M) in the following Env4 (parent Env1) ;; parent from closure for M recur ~~~> [procedure|(arg)|(lambda (arg) ((future future) arg)) |Env3] (lambda (l) (if (null? l) 0 (add1 (recur (cdr l))))) ==> [procedure|(l)|(if (null? l) 0 (add1 (recur (cdr l)))) |Env4] Then we finally have the value needed to bind to length. Then we evaluate the body (of (let ((length...)))) in the environment Env5 (parent Env1): length ~~~> [procedure|(l)|(if (null? l) 0 (add1 (recur (cdr l)))) |Env4] (length '(a)) To evaluate the application we first evaluate the operator and its arguments length ==> [procedure|(l)|(if (null? l) 0 (add1 (recur (cdr l)))) |Env4] '(a) ==> (a) then we evaluate the body (of the closure for length) in Env6 (parent Env4) ;; parent from closure for length l ~~~> [(a)] (if (null? l) 0 (add1 (recur (cdr l)))) = (add1 (recur (cdr l))) To evaluate the application (add1 (recur (cdr l))) we first evaluate the operator and its arguments add1 ==> [procedure|(x)|(+ x 1)|Env0] To evaluate the application (recur (cdr l)) we first evaluate the operator and its arguments recur ==> [procedure|(arg)|(lambda (arg) ((future future) arg)) |Env3] (cdr l) ==> () then we evaluate the body (of the closure for recur) in Env7 (parent Env3): ;; parent from closure for recur arg ~~~> [()] ((future future) arg) To evaluate the application ((future future) arg) we first evaluate the operator and its arguments To evaluate the application (future future) we first evaluate the operator and its aruguments future ==> [procedure|(future)|(M (lambda (arg) ((future future) arg))) |Env2] future ==> [procedure|(future)|(M (lambda (arg) ((future future) arg))) |Env2] then we evaluate the body (of the closure for future in Env8 (parent Env2): ;; why is the parent Env2? future ~~~> [procedure|(future)|(M (lambda (arg) ((future future) arg))) |Env2] (M (lambda (arg) ((future future) arg))) To evaluate this application we first evaluate the operator and its aruguments M ==> [procedure|(recur)|(lambda (l) (if (null? l) 0 (add1 (recur (cdr l)))))) |Env1] (lambda (arg) ((future future) arg)) ==> [procedure|(l)|(if (null? l) 0 (add1 (recur (cdr l)))) |Env8] then we evaluate the body (of the closure for M) in Env9 (parent Env1): ;; why is the parent Env1? recur ~~> [procedure|(l)|(if (null? l) 0 (add1 (recur (cdr l)))) |Env8] (lambda (l) (if (null? l) 0 (add1 (recur (cdr l)))))) ==> [procedure|(l)|(if (null? l) 0 (add1 (recur (cdr l)))) |Env9] this is the result of (M (lambda (arg) ((future future) arg))) and hence of (future future) we continue with the argument to ((future future) arg) arg ==> () then we evaluate the body of the closure for (future future) in Env10 (parent Env9): l ~~~> [()] (if (null? l) 0 (add1 (recur (cdr l)))) ==> 0 so 0 is the value of ((future future) arg) and hence of (recur (cdr l)) so we evaluate the body of the closure (for add1) in Env11 (parent Env0): x ~~> [0] (+ x 1) To evaluate the application (+ x 1) we first evaluate the operator and its arguments + ==> [procedure| ... |Env0] x ==> 0 1 ==> 1 ==> <+ is primitive> 1 6. Here's one with letrec Env0: cons ~~> [primitive-procedure|(x l)|...|Env0] (LETREC ([n-reverse-sublist (LAMBDA (n ls) (LET ([n-dup-on (LAMBDA (to-dup tail) (LETREC ([n-dup (LAMBDA (n*) (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))))]) (n-dup n)))]) (LETREC ([helper (LAMBDA (ls) (IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls)))))]) (helper ls))))]) (n-reverse-sublist 2 '((a b)))) To evaluate the LETREC, we set up a new environment Env1 (parent Env0): n-reverse-sublist ~~~> ?? and in this envrionment we evaluate the expression that will give the value of ?? (LAMBDA (n ls) (LET ([n-dup-on (LAMBDA (to-dup tail) (LETREC ([n-dup (LAMBDA (n*) (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))))]) (n-dup n)))]) (LETREC ([helper (LAMBDA (ls) (IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls)))))]) (helper ls)))) ==> [procedure |(n ls) |(LET ([n-dup-on (LAMBDA (to-dup tail) (LETREC ([n-dup (LAMBDA (n*) (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))))]) (n-dup n)))]) (LETREC ([helper (LAMBDA (ls) (IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls)))))]) (helper ls)))) |Env1] so that Env1 looks like the following, in which we do the body of the LETREC Env1 (parent Env0): n-reverse-sublist ~~~> [procedure|(n ls)|(LET ...)|Env1] (n-reverse-sublist 2 '((a b))) To evaluate the application we evaluate the operator and args n-reverse-sublist ==> [procedure |(n ls) |(LET ([n-dup-on (LAMBDA (to-dup tail) (LETREC ([n-dup (LAMBDA (n*) (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))))]) (n-dup n)))]) (LETREC ([helper (LAMBDA (ls) (IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls)))))]) (helper ls)))) |Env1] 2 ==> 2 '((a b)) ==> ((a b)) then evaluating the body of the closure in the following Env2 (parent Env1): n ~~~> [2] ls ~~> [((a b))] (LET ([n-dup-on (LAMBDA (to-dup tail) (LETREC ([n-dup (LAMBDA (n*) (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))))]) (n-dup n)))]) (LETREC ([helper (LAMBDA (ls) (IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls))))))) (helper ls))) To evaluate the LET we need to compute the value of (LAMBDA (to-dup tail) (LETREC ([n-dup (LAMBDA (n*) (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))))]) (n-dup n))) ==> [procedure |(to-dup tail) |(LETREC ([n-dup (LAMBDA (n*) (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))))]) (n-dup n)) |Env2] Then we evaluate the body of the LET in the following Env3 (parent Env2): n-dup-on ~~> [procedure |(to-dup tail) |(LETREC ([n-dup (LAMBDA (n*) (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))))]) (n-dup n)) |Env2] (LETREC ([helper (LAMBDA (ls) (IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls))))))) (helper ls)) To evaluate the LETREC, we set up a new environment Env4 (parent Env3): helper ~~~> ?? and in this environment we evaluate the expression that will give the value of ?? (LAMBDA (ls) (IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls))))) ==> [procedure |(ls) |(IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls)))) |Env4] so that Env4 looks like the following, in which we do the body of the LETREC Env4 (parent Env3): helper ~~~> [procedure |(ls) |(IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls)))) |Env4] (helper ls) To evaluate the application we first eval the operator and args helper ==> [procedure |(ls) |(IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls)))) |Env4] ls ==> ((a b)) then we evaluate the body of the closure (for helper) in... Env5 (parent Env4): ls ~~> [((a b))] (IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls)))) = (n-dup-on (reverse (car ls)) (helper (cdr ls))) To evaluate the application we first eval the operator and args n-dup-on ==> [procedure |(to-dup tail) |(LETREC ([n-dup (LAMBDA (n*) (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))))]) (n-dup n)) |Env2] (reverse (car ls)) To evaluate the application we first eval the operator and args reverse ==> [primitive-procedure|(ls)|...|Env0] (car ls) ==> (a b) ==> (b a) (helper (cdr ls)) To evaluate the application we first eval the operator and args helper ==> [procedure |(ls) |(IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls)))) |Env4] (cdr ls) ==> () then we evaluate the body of the closure (for helper) in Env6 (parent Env4): ls ~~~> [()] (IF (null? ls) '() (n-dup-on (reverse (car ls)) (helper (cdr ls)))) ==> () then we evaluate the body of the closure (for n-dup-on) in Env7 (parent Env2) ;; why is the parent Env2? to-dup ~~> [(b a)] tail ~~> [()] (LETREC ([n-dup (LAMBDA (n*) (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))))]) (n-dup n)) To evaluate the LETREC, we set up a new environment Env8 (parent Env7): n-dup ~~~> ?? and in this environment we evaluate the expression that will give the value of ?? (LAMBDA (n*) (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*))))) ==> [procedure |(n*) |(IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))) |Env8] so that Env8 looks like the following, in which we do the body of the LETREC Env8 (parent Env7): n-dup ~~~> [procedure |(n*) |(IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))) |Env8] (n-dup n) To evaluate the application we first eval the operator and args n-dup ==> [procedure |(n*) |(IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))) |Env8] n ==> 2 Then we evaluate the body of the closure (for n-dup) in Env9 (parent Env8): n* ~~> 2 (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))) = (cons to-dup (n-dup (sub1 n*))) To evaluate the application we first eval the operator and args cons ==> [primitive-procedure|(x l)|...|Env0] to-dup ==> (b a) (n-dup (sub1 n*)) To evaluate this application we first eval the operator and args n-dup ==> [procedure |(n*) |(IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))) |Env8] (sub1 n*) ==> 1 Then we evaluate the body (of the closure for n-dup) in Env10 (parent Env8): n* ~~> [1] (IF (zero? n*) tail (cons to-dup (n-dup (sub1 n*)))) ==> ((b a)) ==> ((b a) (b a))