Com S 342 meeting -*- Outline -*- * Recursion (5.6) Warning: this is out of order from the book! ** top-level recursion ------------------------------------------ RECURSION FROM TOP-LEVEL DEFINITIONS (5.6) Syntax:
::= | define = define fact = proc(i) if zero(i) then 1 else *(i, fact(sub1(i))); What environment is remembered by the closure? What environment is the closure stored in? ------------------------------------------ ... the global environment (init-env) ... the global environment Draw the picture of how this looks /--------------------------------------\ v | init-env [ *-]--> |-------------| | | + | *-|--> [prim-proc | +] | |-------------| | | ... | | | |-------------| | |fact | *-|--> [closure |* |* | * ] |-------------- | | v v (i) [if | ...] ------------------------------------------ CHANGES DUE TO DEFINE init-env [ *-]--> (extend-env (list 'add1 ...) (list (make-cell (procedure->expressed (make-prim-proc 'add1)) ...)) the-empty-env) What's wrong with the following? (set! init-env (extend-env (list 'fact) (list (make-cell (procedure->expressed (make-closure '(i) (parse "if ...") )))) init-env)) ------------------------------------------ missing init-env as 3rd argument to make-closure but: note that it doesn't give the right recursive structure!! ------------------------------------------ FIXING IT (begin (set! init-env (extend-env (list 'fact) init-env)) ------------------------------------------ ... (list (make-cell (number->expressed 0))) (cell-set! (apply-env init-env 'fact) (procedure->expressed (make-closure '(i) (parse "if ...") init-env))) ) Q: Does this work for mutual recursion? no, still isn't right, as first one won't know about the other fixes: - using another indirection (make environments be cells containing finite functions) - editing existing bindings - use some of the techniques below - make the user do a "forward declaration"! ** letrecproc Q: Will the define trick work in a language with nested recursive procdures? no, won't get right scope if use init-env, and otherwise won't have an environment that stays around... To solve this use another expression ... *** syntax ------------------------------------------ LETRECPROC Concrete syntax: ::= letrecproc in ::= {;}* ::= = Examples: letrecproc fact(x) = if zero(x) then 1 else *(x, fact(sub1(x))) in fact(342) Abstract syntax: (define-record letrecproc (procdecls body)) (define-record procdecl (var formals body)) ------------------------------------------ Q: Why is this restricted to procedure declarations? otherwise we can't do without circular environments, main use anyway *** semantics as a syntactic sugar Q: How could we give a semantics to letrecpoc? It's possible to use tricks from lambda calculus (Y combinator), but that's generally too slow. better: use a syntactic sugar Note: semanticists care about the form of the example, this isn't at all a useful example... ------------------------------------------ SYNTACTIC SUGAR FOR LETRECPROC letrecproc ohNo (i) = wow(wow(i)); wow (x) = ohNo(ohNo(x)) in ohNo(2) ==> ------------------------------------------ ...let ohNo = 0; wow = 0 in begin ohNo := proc(i) wow(wow(i)); wow := proc (x) ohNo(ohNo(x)); ohNo(2) end Q: How does this work? ------------------------------------------ CHANGES DUE TO LETRECPROC init-env [ *-]--> (extend-env (list 'add1 ...) (list (make-cell (procdure->expressed (make-prim-proc 'add1)) ...)) the-empty-env) env1 --> (extend-env (list 'ohNo 'wow) (list (make-cell (number->expressed 0)) (make-cell (number->expressed 0))) init-env) proc(i) wow(wow(i)) ==> (make-closure '(i) (parse "wow(wow(i))") _____) proc (x) ohNo(ohNo(x)) ==> (make-closure '(x) (parse "ohNo(ohNo(x))")))) _____) ------------------------------------------ ... env1 ... env1 Draw picture of how the cells get mutated by the assignment, arrows fom the cells to the closures on this slide. Q: How would you declare this example in C? Use a forward declaration Why is the forward declaration needed? This is an essential homework: to implement the sugar. *** semantics without circular environments (optional) Why? recursive environments may cause problems for tools (looping...) or defining language may be purely functional and have no letrec or defined language may not have assignment! The following is corrected from Fig. 5.6.2 ------------------------------------------ SEMANTICS WITHOUT CIRCULAR ENVIRONMENTS (define-record extended-rec-env (vars vals oldenv)) (define extend-rec-env ;; TYPE: (-> ((list procdecl) ;; Environment) ;; Environment) (lambda (procdecls env) (make-extended-rec-env (map procdecl->var procdecls) (list->vector ; corrected! (map (lambda (procdecl) (make-proc (procdecl->formals procdecl) (procdecl->body procdecl))) procdecls)) env))) (define apply-env ;; TYPE: (-> (Environment symbol) ;; Expressed-Value) (lambda (env var) (variant-case env ; corrected below! (extended-rec-env (vars vals oldenv) (let ((p (ribassoc var vars vals '*fail*))) (if (eq? p '*fail*) (apply-env oldenv var) (else ...)))) ------------------------------------------ vars is a list of procedure names from a letrecproc vals is a vector of proc records from the letrecproc (these together form a rib in the ribcage) in this interpreter we would have have Denoted-Value = Number + Procedure + List(Expressed-Value) Expressed-Value = Number + Procedure + List(Expressed-Value) However, the trick is that the environment doesn't actually store closures for Procedures! ... (procedure->expressed (make-closure (proc->formals p) (proc->body p) env)))) The reason for this is that when a procedure is found, what is returned is a closure, with its own binding Thus the circularity is broken by apply-env which makes a circularity in time. ------------------------------------------ ADDITION TO EVAL-EXP ;;; Figure 5.4.1 : page 154 (no cells!) (define eval-exp (lambda (exp env) (variant-case exp (letrecproc (procdecls body) ------------------------------------------ ... (eval-exp body (extend-rec-env procdecls env))) ...))) ** sugar for letrec with no defined order of evaluation (skip) ------------------------------------------ LETREC SUGAR WITH UNDEFINED EVAL ORDER letrec one2 = cons(1, begin print(12); proc() two1 end); two1 = cons(2, begin print(21); proc() one2 end) in car((cdr(one2))()) ==> let one2 = 0; two1 = 0 in begin let g104 = cons(1, begin print(12); proc() two1 end); g105 = cons(2, begin print(21); proc() one2 end) in begin one2 := g104; two1 := g105; 0 end; car((cdr(one2))()) end ------------------------------------------ Q: with our current sugar, which should get printed first: 12 or 21? 12 Q: Do we want to say that in the language definition? probably not... Q: What in Scheme doesn't have a defined order of evaluation? procedure arguments, thus the sugar