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.