joint_acct

This is a review of Problem 3.7 from Structure and Interpretation of Computer Programs, the make-joint problem.

The rectangle at the top of the drawing is the global environment. When you type something into the interpreter, that is where you start.

Here is the code for make-account, which is slightly different than the one in the book, but still contains the important stuff:

(define (make-account balance password)
  (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 pwd)
    (cond ((not (eq? pwd password)) (error "Wrong password!"))
          ((eq? m 'withdraw) withdraw)
          ((eq? m 'deposit) deposit)
          ((eq? m 'balance) balance)   ; Should there be a balance procedure instead? It would make for uniform syntax...
          (else (error "Unknown request -- MAKE-ACCOUNT"
                       m))))
  dispatch)

When this code is run, the procedure object (the thing with the circles and arrows) for make-account is drawn. The left circle points at the parameters and body of code for the make-account procedure; note that the body is all of the code within the make-account procedure and is omitted from the diagram for brevity. There are two parameters, balanceand password. The right circle points back to the global environment because make-account was defined in the global environment.

And that's it. None of the procedures inside of make-account are visible in the global environment. You cannot, for example, use the withdraw procedure as it is tucked away inside of make-account. It requires execution of make-account to bring them into existence.

Now suppose that the following line of code is run:

(define peter (make-account 1000 'OPPAN))

Here's what happens:

  1. make-account is called, so a frame for make-account is drawn.
  2. The parameter balance for make-account is bound to the argument 1000 and password is bound to 'OPPAN.
  3. The frame points back to the global environment because make-account was defined in the global environment. Frames always point back at the environments to which their right circles point. Always. No exceptions.
  4. Now the body of make-account is executed. This results in the definition of withdraw, deposit, and dispatch, so procedure objects for each are created and the symbols withdraw, deposit, and dispatch are bound to those procedure objects.
  5. The last line of code in make-account is merely dispatch, which is a procedure. Since that is what is being returned, peter in the global environment is bound to the dispatch procedure object. Any time peter is called, a frame will be created, pointing back at the make-account environment because that is where dispatch was defined. If work is being done in such a frame, when it needs to look up the symbol balance will find it here.

So, Peter has a bank account. If Peter wants to deposit 500 monetary units, he does:

((peter 'deposit 'OPPAN) 500)

The dispatch frame is created, the code inside dispatch is executed, and the deposit procedure is returned. The second left parenthesis before peter ensures that deposit will be executed with input 500, and the diagram reflects the change to balance. Since there is no reason for either the deposit or dispatch frames to remain in memory, they are purged, as indicated in the diagram where they are crossed off.

Now suppose the next line of code is run:

(define paul (make-joint peter 'OPPAN 'GANGNAM-STYLE))

The result should be that transactions can now be made on peter by using paul.

Consider the setup of such an account in real life. If Peter already has a bank account, Paul could not simply decide he wants access to it. Peter would accompany Paul to the bank and they would both sign paperwork. Peter would prove to the bank that he was (a) Peter, (b) knew his password, and (c) was OK with Paul also having access to the account. Paul would then be given access to the account, but Paul would have his own password. Paul's password would not work with Peter's ATM card; nor would Peter's work with Paul's. That way, it is possible for the bank to keep records on who deposited, withdrew, lost a card, etc.

So, Paul will need Peter to provide information at the time of the setup, after which Paul will have his own account and password, but with a common, shared balance.

Imagine for a moment that this is the case. The only way that Paul can have a shared balance with Peter is if Paul can access the state variable, balance, in Peter's make-account frame. Note the define that creates paul; it contains peter, which, when executed, will have access to balance. If Peter's password is input to make-joint, Paul's account will have access to it as well. Interestingly, Paul will not have to know that password in order to access the account even though his account must know Peter's password. As long as Peter's password was correct for Peter's account, Paul will be able to do transactions with Peter's account. This leads us to make-joint:

(define (make-joint existing-acc existing-pass new-pass) 
  (cond ((not (existing-acc 'deposit existing-pass))
         (error "Wrong password for original account"))
        (else (lambda (msg pass)
                (cond ((eq? pass new-pass)
                       (existing-acc msg existing-pass))
                      (else (error "WRONG PASSWORD")))))))

The variable names in the environment diagram exacc, expwd, and newpd are shorthand for existing-acc, existing-pass, and new-pass. Here is what happens when make-joint is executed in terms of the environment diagram.

  1. make-joint is called, so a frame for make-joint is drawn.
  2. existing-acc, existing-pass, and new-pass are bound to peter (the procedure), 'OPPAN, and 'GANGNAM-STYLE. (We're pretty sure that this is what the Korean star Psy had in mind when he wrote the song.) Note that if the wrong password were used to access peter, an error would have prevented paul from being set up as a joint account.
  3. The frame points back to the global environment because make-joint was defined in the global environment.
  4. Instead of a named dispatch procedure, make-joint returns an unnamed lambda. But the behavior is the same as if it were named; paul gets bound to this lambda.

Now if Paul wants to know the balance in the account, he does:

(paul 'balance 'GANGNAM-STYLE)

Paul's password is verified, and Peter's account receives the 'balance message along with Peter's password. Since Peter's account points back to his make-account frame, this call to dispatch with the 'balance argument will as well. This allows Peter's account balance to be looked up and returned to Paul.

If this were done in a language that had built-in classes, such as Java, instead of make-account, we would have an Account class, and peter and paul would be objects constructed by means of the Account class. So what we have here with a few small chunks of Scheme code is how procedure objects can be used to do object-oriented programming because lambdas allow state variables to work!