Com S 342 meeting -*- Outline -*- * Call-by-Name and Call-by-Need (6.5) ** call-by-name *** informal overview (using the indirect array model) Call-by-name was first used in Algol 60 **** motivation Q: Does any parameter mechanism correspond to beta-reduction? would be inefficient to implement that directly, perhaps as rewriting whole procedures may be time/space inefficient ------------------------------------------ LEFTMOST-ORDER MECHANISM? % does this work? --> define while = proc(test, body) if equal(test, 1) then begin body; while(test,body) end else 0; --> define not = proc(b) if equal(b, 0) then 1 else 0; --> define prod = proc(ls) local result = 1 in begin while(not(null(ls)), begin result := *(result, car(ls)); ls := cdr(ls) end); result end; --> define myls = list(1,2,3); --> prod(myls); ------------------------------------------ (The local construct is a version of let...) with call-by-value, or value-result, the while procedure either gets into an infinite loop, or stops right away with call-by-reference, it could stop, but only if the test were variable the body is only evaluated once anyway... Works with call-by-name; but prod(list(1,2,3)) doesn't work, because, like call-by-reference, passing a value gets a new cell each time the expression is evaluated... Q: What might some advantages of giving users the ability to define things like while? - can define their own control easier, - less built-in to language. **** array subscripts as parameters ------------------------------------------ SUBSCRIPTED ARRAY PARAMETERS PASSED BY NAME --> local i = 0 in local p = proc(x) begin i := 1; x := 2 end in letarray a[2] in begin a[0] := 1; p(a[i]); writeln(a[0], a[1]) end; ------------------------------------------ ... 1 2 draw picture **** careful! ------------------------------------------ CAN WE JUST PASS PARAMETER TEXTS? --> define constant = proc(x) proc(y) x; --> (constant(3))(4); 3 --> define ohno = proc(x) ohno(x); --> (constant(3))(ohno(4)); 3 --> define y = 3; --> (constant(y))(4); ------------------------------------------ Get capture if just pass the parse tree for "y" along, (call-by-text) and in that case answer would be 4, not 3. Q: How could we avoid the capture? pass not just the text, but it's environment this is essentially a closure, but has no parameters, called a thunk. *** model (interpreter/language changes) for call-by-name, indirect arrays **** domains and data structures What is passed in call-by name is some kind of thunk ------------------------------------------ DOMAINS FOR CALL BY NAME WITH INDIRECT ARRAYS Environment = -> Denoted-Value Denoted-Value = Expressed-Value = Number + Procedure + Array(Expressed-Value) Procedure = prim-proc + closure Array(T) = {Cell(T)}* L-Value = Thunk = New data structures: ------------------------------------------ ... Denoted-Value = L-value + Thunk ... L-Value = Cell(Expressed-Value) + Array-Element(Expressed-Value) ... Thunk = () -> L-Value So thunks are thought of as parameterless functions that return an l-value use same ideas for L-values as call-by-reference; array-elements are still around We represent thunks by transforming the procedural idea to a record: ... (define-record thunk (exp env)) **** changes to interpreter see $PUB/lib/ch6-5-2.scm for the whole thing This is different than the book. ------------------------------------------ CHANGES TO THE CALL-BY-VALUE INTERPRETER FOR CALL-BY-NAME (INDIRECT MODEL) ;;; *Modified* from Figure 6.5.1 (define eval-rand ;; TYPE: (-> (parsed-exp Environment) ;; Denoted-Value) (lambda (rand env) (variant-case rand ------------------------------------------ ... (varref (var) (let ((den-val (apply-env env var))) (if (thunk? den-val) den-val (make-thunk rand env)))) (else (make-thunk rand env))))) The test prevents making thunks containing thunks, no need for that. All expressions other than varrefs are wrapped in a thunk, this is what you should think of as the mechanism. Note that this is different than the eval-rand in the book, which we call eval-thunk Q: How and when do we get the value? when we apply a primitive procedure, use denoted->expressed... Q: What kinds of parameters are there? variables, array refs, and others Q: Why don't we have a special case for array refs? because we're going to make a thunk for everything, we just don't want to have thunks containing thunks. ------------------------------------------ EXTRACTING VALUES ;;; (unchanged code from) Figure 6.5.2 (define denoted->expressed ;; TYPE: (-> (Denoted-Value) ;; Expressed-Value) (lambda (den-val) (let ((l-val (denoted->L-value den-val))) (cond ((cell? l-val) (cell-ref l-val)) ((ae? l-val) (array-ref (ae->array l-val) (ae->index l-val))) (else (error "Can't deref: " l-val)))))) ;;; *Modified* from Figure 6.5.2, page 204 (define denoted->l-value ; TYPE: (-> (Denoted-Value) L-value) (lambda (den-val) (if (thunk? den-val) ------------------------------------------ ... (eval-thunk den-val) ;; changed den-val))) Q: What should eval-thunk do? ------------------------------------------ EXTRACTING VALUES (CONTINUED) ;;; *Modified* from Figure 6.5.2 (define eval-thunk ;; new name/type ;; TYPE: (-> (Thunk) L-value) (lambda (thnk) (variant-case thnk (thunk (exp env) (variant-case exp (else (error "Not a thunk: " thnk))))) ------------------------------------------ ... (varref (var) (denoted->l-value (apply-env env var))) (arrayref (array index) (make-ae (expressed->array (eval-array-exp array env)) (expressed->number (eval-exp index env)))) (else (make-cell (eval-exp exp env))))) Note that the above uses expressed->denoted instead of make-cell Q: What has to be done to assign to a denoted value? What cases? ------------------------------------------ MORE CHANGES TO THE CALL-BY-VALUE INTERPRETER FOR CALL-BY-NAME (INDIRECT MODEL) (define denoted-value-assign! ;; TYPE: (-> (Denoted-Value ;; Expressed-Value) void) (lambda (den-val exp-val) (let ((l-val (cond ((cell? den-val) (cell-set! den-val exp-val)) ((ae? l-val) (else (error "Can't assign to :" den-val))))) ------------------------------------------ ... (denoted->L-value den-val))) ... (array-set! (ae->array den-val) (ae->index den-val) exp-val)) Q: What is the value of let x = 3 in begin x := 4; x end (remember that let is a syntactic sugar)? it's 3! Q: So do we have any variables? no, they are gone. to restore them, use define, or the following. ------------------------------------------ LOCAL VARIABLES Concrete syntax: ::= local in Abstract Syntax: (define-record local (decls body)) ;; changes to base interpreter: (define eval-exp ; TYPE: (-> (parsed-exp Environment) ; Expressed-Value) (lambda (exp env) (variant-case exp ; ... (local (decls body) (let ((vars (map decl->var decls)) (exps (map decl->exp decls)) ) (let ((new-env (extend-env vars (map (lambda (exp) ; following is new! (expressed->denoted (eval-exp exp env))) exps) env))) (eval-exp body new-env)))) (else ...)))) ------------------------------------------ ** call by need *** motivation call by name confusing with assignments and other side effects if no side effects, then each time the thunk is called, it gives back same value. How could this be optimized? (Needs examples) *** model ------------------------------------------ DOMAINS FOR CALL-BY-NEED (LAZY EVALUATION) WITH INDIRECT ARRAYS Environment = -> Denoted-Value Denoted-Value = Expressed-Value = Number + Procedure + Array(Expressed-Value) Procedure = prim-proc + closure Array(T) = {Cell(T)}* L-value = Cell(Expressed-Value) + Array-Element(Expressed-Value) Thunk = () -> L-Value Memo(T) = ------------------------------------------ ... L-value + Memo(L-Value) ... Cell((() -> T) + T) Q: What's the information in a memo? How could you make a record? (define-record memo (cell)) We won't use memo records directly (unlike the book), but instead will use a memo ADT, as it's a useful concept in general. ------------------------------------------ A MEMO ADT ;;; Anonymous figure : page 207 (define-record memo-rep (cell)) (define memo? memo-rep?) (define memo-remember ;; TYPE: (-> ((-> () T)) (memo T)) (lambda (thnk) (make-memo-rep (make-cell thnk)))) (define memo-retrieve ;; TYPE: (-> ((memo T)) T) (lambda (m) (variant-case m (memo-rep (cell) ------------------------------------------ (let ((contents (cell-ref cell))) (if (procedure? contents) (let ((val (contents))) (begin (cell-set! cell val) val)) contents))) (else (error "bad argument to memo-retrieve:" m))))) ------------------------------------------ DENOTED VALUES FOR CALL-BY-NEED (load-quietly-from-lib "memo.scm") ;;; Anonymous figure : page 202 (define-record thunk (exp env)) (define memo-create ;; TYPE: (-> (parsed-exp Environment) ;; Denoted-Value) (lambda (exp env) ;; eval-thunk as before (define l-value->denoted ;; TYPE: (-> (L-Value) Denoted-Value) inject) (define denoted->l-value ;; TYPE: (-> (Denoted-Value) L-value) (lambda (den-val) (if (memo? den-val) (memo-retrieve den-val) den-val))) ------------------------------------------ ... (memo-remember (lambda () (eval-thunk (make-thunk exp env)))))) **** model interpreter changes ------------------------------------------ CHANGES FOR CALL-BY-NEED INTERPRETER MAKING MEMOS ;;; *Modified* from Figure 6.5.3, page 208 (define eval-rand ; TYPE: (-> (parsed-exp Environment) ; Denoted-Value) (lambda (rand env) (variant-case rand (varref (var) (let ((den-val (apply-env env var))) ------------------------------------------ ... (if (memo? den-val) den-val (memo-create rand env)))) (else (memo-create rand env))))) ------------------------------------------ CHANGES FOR CALL-BY-NEED INTERPRETER EXTRACTING VALUES ;;; *Modified* from Figure 6.5.3, page 208 (define denoted->l-value ; TYPE: (-> (Denoted-Value) L-value) (lambda (den-val) ------------------------------------------ (if (memo? den-val) (memo-retrieve den-val) den-val)))