Com S 342 meeting -*- Outline -*- * Recursion (3.6) ** the problem ------------------------------------------ WHY DOESN'T LET WORK FOR RECURSION? What does the interpreter with let do for the following? let fact = proc(x) if zero?(x) then 1 else *(x, (fact sub1(x))) in (fact 342) ------------------------------------------ Q: How could we fix that? It's possible to use tricks from lambda calculus (Y combinator), see exercise 3.21 on p. 89, for example, but that's generally too slow and too tricky to be practical. So we want a new special form. The goal is to allow recursive procedures to be defined easily, and to support mutual recursion. We restrict this to the definition of recursive procedures (as opposed to recursive data in general). This simplifies the treatment. It's the main use anyway... ** syntax ------------------------------------------ LETREC EXPRESSIONS Examples: letrec fact(x) = if zero?(x) then 1 else *(x, (fact sub1(x))) in (fact 342) letrec even(n) = if zero?(n) then 1 else (odd sub1(n)) odd(k) = if zero?(k) then 0 else (even sub1(k)) in (odd 541) Concrete syntax: ::= ------------------------------------------ ... letrec { ({>}*(,) ) = }* in ------------------------------------------ Abstract syntax: (define-datatype expression expression? ... (letrec-exp (proc-names (list-of symbol?)) (idss (list-of (list-of symbol?))) (bodies (list-of expression?)) (letrec-body expression?)) ) ------------------------------------------ ** semantics The treatment of the semantics of letrec in the book is a beautiful example of the use of abstraction in programming. Q: What do we want letrec to do, in essence? Run the letrec-body in an environment where each procedure name is bound to an appropriate closure. So let's code that and leave the details for later: ------------------------------------------ (deftype eval-expression (-> (expression environment) Expressed-Value)) (define eval-expression (lambda (exp env) (cases expression exp (var-exp (id) (apply-env env id)) ; ... (proc-exp (ids body) (procval->expressed (closure ids body env))) (letrec-exp (proc-names idss bodies letrec-body) (eval-expression letrec-body (extend-env-recursively proc-names idss bodies env))) ))) ------------------------------------------ Now the only trick is to specify and implement extend-env-recursively. ------------------------------------------ SPECIFICATION OF EXTEND-ENV-RECURSIVELY Let e2 = (extend-env-recursively proc-names idss bodies e) - if name is in proc-names and if ids and body are the corresponding formal parameter list and procedure body, then (apply-env e2 name) = (closure ids body e2) - if name is not in proc-names, then (apply-env e2 name) = (apply-env e name) ------------------------------------------ ** implementation So we have the environment abstraction with constructors: empty-env, extend-env, extend-env-recursively and observer: apply-env Q: What different ways could we represent environments? how have we done it so far? three ways... *** procedural representation ------------------------------------------ PROCEDURAL REPRESENTATION OF ENVIRONMENTS ; (defrep (environment (-> (symbol) Expressed-Value))) (define apply-env (lambda (env sym) (env sym))) ;; ... (define extend-env (lambda (ids vals env) (lambda (sym) (let ((pos (list-index sym ids))) (if (<= 0 pos) (list-ref vals pos) (apply-env env sym)))))) ;; Figure 3.9, see ch3-6-1.scm (define extend-env-recursively (lambda (proc-names idss bodies old-env) ------------------------------------------ Q: How does this satisfy the specification? ... (letrec ((rec-env (lambda (sym) (let ((pos (list-index sym proc-names))) (if (<= 0 pos) (procval->expressed (closure (list-ref idss pos) (list-ref bodies pos) rec-env)) (apply-env old-env sym)))))) rec-env))) *** AST representation Now we can transform this to the AST representation... (if editing, take out the defrep) ------------------------------------------ AST REPRESENTATION OF ENVIRONMENTS (define-datatype environment environment? ; ... (extend-env-recursively (proc-names (list-of symbol?)) (idss (list-of (list-of symbol?))) (bodies (list-of expression?)) (env environment?))) (define apply-env (lambda (env sym) (cases environment env ;; ... (extend-env-recursively (proc-names idss bodies old-env) (let ((pos (list-index sym proc-names))) (if (<= 0 pos) (procval->expressed (closure (list-ref idss pos) (list-ref bodies pos) env)) (apply-env old-env sym))))))) ------------------------------------------ *** Optimizations Q: How often are closures built? each time the name is looked up Q: Is that necessary? No One way to fix this is to memoize the environments (exercise 3.35). More usual is to build the closures once. If we are to build the closures once, we are going to replace an unfolding of the data structure over time with a single closure object that has the right environment in it (a fixpoint). Hence we'll have a circular environment. Another way to think of this: Recall that we want e2 = (extend-env-recursively proc-names idss bodies e) to be such that, if name is in proc-names and if ids and body are the corresponding formal parameter list and procedure body, then (apply-env e2 name) = (closure ids body e2) This suggests that e2 should be a circular data structure constructed in this way. ------------------------------------------ MUTUALLY RECURSIVE ENVIRONMENTS AND CLOSURES letrec even(n) = if zero?(n) then 1 else (odd sub1(n)) odd(k) = if zero?(k) then 0 else (even sub1(k)) in ... We want e2 = (extend-env-recursively '(even odd) '((n) (k)) (list <> <>) e) to be such that, (apply-env e2 even) = (closure '(n) < e2) (apply-env e2 odd) = (closure '(k) < e2) ------------------------------------------ Draw the picture of how this looks e-\ | v [extended-env-record | * | * | * ] ... ^ | e2-\ | | /----------------------------+-------------------------------------\ | | | | v v | | [extended-env-record | * | * | * ] | | | | /---------------/ | | v \---->|---| /---->[closure | * | * | *-]-| even <-[-* | * ] | *-|-/ | | | | |---| v v | /------/ | *-|-\ (n) <<...>> | v |---| | | odd <-[-* | / ] v | [closure | * | * | *-]------------/ | | v v (k) <> ------------------------------------------ THE CODE (ch3-6-3.scm) (define-datatype environment environment? (empty-env-record) (extended-env-record (syms (list-of symbol?)) (vec vector?) (env environment?)) ) (define extend-env-recursively (lambda (proc-names idss bodies old-env) ------------------------------------------ ... (let ((len (length proc-names))) (let ((vec (make-vector len (procval->expressed (closure '() (lit-exp 0) (empty-env)))))) (let ((env (extended-env-record proc-names vec old-env))) (for-each (lambda (pos ids body) (vector-set! vec pos (procval->expressed (closure ids body env)))) (iota len) idss bodies) env))))) point out the use of vector-set! Here's the code for iota: (deftype iota (-> (number) (list-of number))) (define iota (lambda (end) (let loop ((next 0)) (if (>= next end) '() (cons next (loop (+ 1 next))))))) ** semantic implications and variations Q: Do mutually recursive environments and closures cause any problems for tools? Yes, the circularity is a problem for debuggers, sending values, ... Q: What about top-level definitions? How to make them recursive? This is a homework problem Q: How would you do a "namedlet" expression? SLLGEN input: (expression ("namedlet" identifier "(" (separated-list identifier "=" expression ",") ")" expression) namedlet-exp) ASTs: (namedlet-exp (proc-name symbol?) (ids (list-of symbol?)) (exps (list-of expression?)) (body expression?)) In eval-expression: (namedlet-exp (proc-name ids exps body) (eval-expression (app-exp (var-exp proc-name) exps) (extend-env-recursively (list proc-name) (list ids) (list body) env))) Test case (see ch3-6-2-modified-in-class.tst): (run "namedlet fact (x = 3) if zero?(x) then 1 else *(x, (fact sub1(x)))")