Quirksand

SICP

SICP 3.1.3 Solutions

August 6, 2017 11:01

Exercise 3.7

This one doesn’t require modifying the account creation that already exists; what we’ll do instead is delegate any actions to the original account. Since that account is already considered to exist when we create a joint one, there is no need to do anything in make-joint other than to check the password and pass on the command to the original account if the password is correct. The joint account needs to work in the same way as the original, which means that make-joint must return a procedure. In this case, the ‘dispatch’ of that procedure only does one thing – check the joint account-holder’s password. If it matches, it makes a call to the original account using the original password.

There is one point of contention however. That concerns the response to invalid passwords. If make-account has a separate password-checking procedure from make-joint, then we potentially have two separate places where the checking code exist. Future changes, such as adding the ‘call-the-cops’ alarm for example, would require changes in both procedures. Doing the same thing in two places isn’t generally a good practice, especially when from an external point of view the entity (the ‘account’, whether joint or single) is expected to behave the same. The users (‘bank customers’ in this case) should be able to interact with the joint account exactly as if it were a regular account. That goes even for improper interactions too; perhaps especially so.

There are several approaches to this problem. It might be feasible to delegate password responses to the original account, by delivering either a valid or invalid original password depending on whether the joint accountholder’s password is valid. We could also modify make-account to incorporate joint accounts as a special case, but there are many situations in which the original code can’t be easily modified. A robust way to deal with this is to have a separate password-checking processor. That way, both original accounts and joint accounts would always be working with the same system. Of course, that would require at least some changes to make-account, but it’d be a smaller change than adding in joint accounts as a special case.

Exercise 3.8

If we’re considering that the addition will yield different results depending on the evaluation order of its arguments, then we have to think about what it means when we change the evaluation order. If we’re going ‘left-to-right’, that means the arguments are evaluated in sequence, with the leftmost argument evaluated first, then the next one, and so on. If it’s ‘right-to-left’, then the rightmost argument is evaluated first, in reverse fashion from the other order. Which means what we really want is for the procedure f to give different results depending on what arguments it has been called with in the past.

In order for the results of a procedure to always be different depending on when it is called, we need some sort of stored state. (Randomness would give different results, but none that we could rely on). Our function f will be designed similarly to those in previous exercises, where a let variable is preserved locally in a lambda expression.

Some open questions with the exercise are whether the procedure f only needs to exhibit this behavior one time, or if it can change when it’s repeated. We also don’t know if it can accept other input values besides 0 or 1. It’s definitely much easier to design one that can give the answer without any of those conditions; I originally had a solution that only fulfills the requirements of the text, but then came up with one that’s more versatile.

My first attempt (a ‘filtering buffer’) will work only with the expressions as given in the text, although it can handle repeated calls as long as certain conditions are met. In that version the saved state is just a toggle that switches back and forth. It is thus only valid as long as the calls to f come in pairs, and the input limited to the values 0 and 1.

My second approach does not have limitations on its input (other than that it is numeric), and works whatever the previous calls to f were. It saves two variables; one is the last input received, and another which keeps track of the last returned value. Here’s the procedure:

(define f
  (let ((returned 0)
        (last-in 0)
        )
        (lambda(x)
          (set! returned (+ (- returned) last-in))
          (set! last-in x)
          returned
          )
    )
  )

This function returns a value that is the negation of the most recently returned value, added to the most recent input. Suppose we call it with some value x as the input. It will save x as the last input value and return some value, which we’ll call a. We can’t know what a is in this example, since it depends on past calls. We do know that on the next call, it will return -a + x since x is currently stored as the last value. Therefore if we sum the results of two consecutive calls, we get aa + x, which is simply x. This procedure works in such a way that summing the result of any two sequential calls yields whatever the input to the first call was. In this exercise, that means our sum will come out to whatever argument is evaluated first, whether it be from the left or from the right.

Solutions 3.1.3.