Search code examples
referenceschemelispsicplexical-scope

Lexical Scoping and sharing objects


Consider the make-account procedure in SICP.

(define (make-account balance)
  (define (withdraw amount)
    (if (>= balance amount)
        (begin
          (set! balance (- balance amount))
          balance)
        "Insufficient funds"))
  (define (deposit amount)
    (set! balance (+ balance amount))
    balance)
  (define (dispatch m)
    (cond ((eq? m 'withdraw) withdraw)
          ((eq? m 'deposit) deposit)
          (else (error "Unknown request: MAKE-ACCOUNT" m))))
  dispatch)

And the example:

(define peter-acc (make-account 100))
(define paul-acc peter-acc)

And the footnote:

The phenomenon of a single computational object being accessed by more than one name is known as aliasing. The joint bank account situation illustrates a very simple example of an alias.

...

Bugs can occur in our programs if we forget that a change to an object may also, as a “side effect,” change a “different” object because the two “different” objects are actually a single object appearing under different aliases. These so-called side-effect bugs are so difficult to locate and to analyze that some people have proposed that programming languages be designed in such a way as to not allow side effects or aliasing

..."

In normal situations, I would here people say: "paul-acc refers to peter-acc".

As I understand it, peter-acc and paul-acc are really names that point to one computational object. So they are the same.

I am confused as to how it would be modeled in the environment model of evaluation. For example:

(define (f bank-account) ((bank-account 'deposit) 69))

(define peter-acc (make-account 100))
(define paul-acc peter-acc)
(f paul-acc)

I cannot do environment diagrams because my eyes are destroyed. Here's what I think the interaction should be:

  • make-account and f have pointers to the global environment.
  • (define peter-acc (make-account 100)) is evaluated. make-account creates a new environment e1. Enclosing environment is global. The internal procedures withdraw, deposit and dispatch are created and have pointers to e1. Dispatch is returned and is bound to the name peter-acc in the global environment.
  • (define paul-acc peter-acc) is evaluated. The name peter-acc is found in the global frame. paul-acc is bound to the dispatch procedure object in e1 because that is where peter-acc is pointing to. Therefore, Dispatch in e1 is bound to the names peter-acc and paul-acc in the global environment.
  • (f paul-acc) is evaluated. A new environment e2 is created by f. Enclosing environment is global. paul-acc is found in global. In e2, banck-account is bound to the dispatch procedure object in e1 because that is where paul-acc is pointing to. Therefore, peter-acc with respect to the global environment, paul-acc with respect to the global environment, and bank-acount with respect to e2 all point to the dispatch procedure in e1.
  • The body gets executed.

Is this all correct?

The thing that confuses me is when I encounter things like this in SICP exercises, when constructing environment diagrams, I read people on the web saying things like "bank-account refers to paul-acc. Paul-acc refers to peter-acc." Why exactly is the word "refers" used here? Does bank-account with respect to e2 actually point to the name paul-acc and not it's value?


Solution

  • Your points 2 to 5 are correct. In your point 1, f and make-account do not "have" pointers to global environment - they do not need to, by themselves. They are both entries, bindings, in the global environment. Both "refer to", or "point at" simple values, functions in both cases.

    bank-account with respect to e2 actually points to the value to which paul-acc points (which is the same value to which peter-acc points, or refers). In Scheme, (define n1 n2) means "set up new binding in the current environment, named n1, and pointing at the value of the expression n2". If n2 happens to be a variable, its value is just what that variable's value is. That's why we're talking about Scheme's evaluation semantics.

    A function call (fun arg) is evaluated by finding the value of arg expression, binding the function's parameter to this value, and then evaluating the function's body in the resulting environment:

    ( (lambda (param) body) arg )
    =
    (let ( (param arg) )
       body)