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.