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:
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.
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!