prev up next

9) Object behaviour change

Here is a program which, like that in part 8 sets up a bank account into which money may be deposited and withdrawn. In this case, however, the customer had the option of upgrading the bank account to one with an overdraft facility. The command u causes an automatic upgrading. In this example, each request for a withdrawal that would lead to a negative balance is considered by the bank through a separate bank object. For simplicity, the bank object just prompts for a yes/no answer, and since we are using just simple text interface, the prompt appears alongside the interaction with the customer. This example also cleans up a loophole in the part 8 example which enabled an overdraft to be given by depositing a negative amount.

#main ==
   Screen<-aio\stdio(),
   Acc<-ordinaryAccount(Bank),
   Bank<-bank(Screen),
   customer(Screen,Acc);

#ordinaryAccount(Bank,balance<-0)~
{
 deposit(n) | balance+=n;
 withdraw(n)-
   [
    n<=balance |>=, balance-=n;
    :          |>#
   ]
 balance-|>balance;
 upgrade || <=overdraftAccount(Bank,balance);
 cancel |
}

#overdraftAccount(Bank,balance)~
{
 deposit(n) | balance+=n;
 withdraw(n)-
   [
    n<=balance |>=, balance-=n;
    :          | ?Bank.ask(n-balance)
                 :>=,balance-=n;
                 :>#
   ]
 balance-|>balance;
 upgrade |;
 cancel || <=ordinaryAccount(Bank,balance<-balance);
}

#bank(Screen)~
{
 ask(overdraft)-| Screen
                    .fwrite("Bank: grant overdraft of $">+overdraft>"? ")
                    .readString->answer,
                  <(answer)
                  {
                   answer="y" ||>=;
                   answer="n" ||>#;
                   : | Screen
                         .fwrite("Invalid reply, re-enter: ")
                         .readString->answer
                  }
}

#customer(Screen,Acc)=
{
 | Screen.fwrite("Customer: ").readString->comm
   <(comm)
   {
    comm="d" | Screen.readInt->amount 
               <(amount)
               {
                amount=error | 
                     Screen.fwrite("Please enter a number only: ")
                           .skip
                           .readInt->amount;
                amount<0     |
                     Screen.fwrite("Please enter a positive amount: ")
                           .readInt->amount;
                : ||| Screen.fwrite("Deposited $">+amount).nl,
                      Acc.deposit(amount);
               }
    comm="w" | Screen.readInt->amount,
               <(amount)
               {
                amount=error |
                     Screen.fwrite("Please enter a number only: ")
                           .skip
                           .readInt->amount;
                : ||| <(amount)
                      ?Acc.withdraw(amount)
                      : Screen.fwrite("Withdrew $">+amount).nl;
                      : Screen.fwrite("Insufficient funds\n");
                }
    comm="b" || Screen.fwrite("Balance is $">+Acc.balance).nl;
    comm="u" || Screen.fwrite("Account upgraded\n"),Acc.upgrade;
    comm="c" || Screen.fwrite("Overdraft facility cancelled\n"),Acc.cancel;
    comm="q" |||;
    :        || Screen.fwrite("Invalid command\n");
   }
}
The main new concept in this example is the <= operator, used in ordinaryAccount and overdraftAccount. The following rule in ordinaryAccount:
upgrade || <=overdraftAccount(Bank,balance);
means that if an ordinaryAccount object receives an upgrade message, future messages to the object are directed to the new overdraftAccount object created by the call overdraftAccount(Bank,balance). The double bar in the rule means that the ordinaryAccount object does not remain in existence after this redirection. In effect, the ordinaryAccount object has become an overdraftAccount object, hence <= is referred to as the become operator. Note that a similar rule in overdraftAccount causes an overdraftAccount object to become a ordinaryAccount on receipt of a cancel message.

The change of object type is not visible to other processes sharing it, except through observed change of behaviour. In fact the customer process here does not keep a record of whether the account it is sending messages to is an ordinaryAccount or an overdraftAccount. Because of this, the code for both is written to handle the full range of messages for either, so an overdraftAccount object can take an upgrade message, and an ordinaryAccount object can take a cancel message, in both cases doing nothing in response.

An alternative way of getting the same effect would be to embed the code for overdraftAccount inside the code for ordinaryAccount, as below:

#ordinaryAccount(Bank,balance<-0)~
{
 deposit(n) | balance+=n;
 withdraw(n)-
   [
    n<=balance |>=, balance-=n;
    :          |>#
   ]
 balance-|>balance;
 upgrade | {
            deposit(n) | balance+=n;
            withdraw(n)-
              [
               n<=balance |>=, balance-=n;
               :          | ?Bank.ask(n-balance)
                            :>=,balance-=n;
                            :>#
              ]
            balance-|>balance;
            upgrade |;
            cancel ||
           }
 cancel |
}
In this case, although it is not possible to create an overdraftAccount directly, the same effect can be obtained by creating a ordinaryAccount and then sending it an upgrade message.

The code with the embedded code for the upgraded account contains duplication of the rules for handling deposit and balance messages. These are unchanged in the upgraded account. Aldwych allows a more concise way of expressing this:

#ordinaryAccount(Bank,balance<-0)~
{
 withdraw(n)-
   [
    n<=balance |>=, balance-=n;
    :          |>#
   ]
 upgrade | {
            withdraw(n)-
              [
               n<=balance |>=, balance-=n;
               :          | ?Bank.ask(n-balance)
                            :>=,balance-=n;
                            :>#
              ]
            upgrade |;
            cancel ||
           }
 cancel |;
 =
 deposit(n) | balance+=n;
 balance-|>balance
}
If an = symbol follows the semiccolon at the end of a rule in a rule set, the rules following it apply not only to that rule set but also to any embedded rule sets in the rules before the = symbol.

prev up next