Here is a program that sets up a system of a customer using a bank account:
#main == Screen<-aio\stdio(), Acc<-account(), customer(Screen,Acc); #account~ <(balance<-0) { deposit(n) | balance+=n; withdraw(n)- [ n<=balance |>=, balance-=n; : |># ] balance-|>balance } #customer(Screen,Acc)= { | Screen.fwrite("Command: ").readString->comm <(comm) { comm="d" || Screen .readInt->amount .fwrite("Deposited $">+amount) .nl, Acc.deposit(amount); comm="w" || Screen.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="q" |||; : || Screen.fwrite("Invalid command\n"); } }The bank account is an object. In this case,
withdraw
messages return a boolean value saying whether the withdrawal was
successful, and the account does not allow withdrawals which would
make the account balance negative. The balance is initialised to
0 when the account is set up.
A customer
procedure here has no return value. It consists
simply of a loop which repeatedly sends messages to two objects, one
the screen with which it interacts, the other the bank account.
Although it has a single rule, this rule has a single bar. The
rule's lhs is empty, which means that it is always applicable. Thus it
would appear a call to the customer
procedure would loop
forever. The reason it does not is that it has an inner set of
selection rules, one of which is triple-barred.
The scope of the fields Screen
and Acc
extend into the inner rule set. An additional field, comm
is given a value as a local variable to the single rule and passed into
the inner rule set. This stores a command read in from the screen.
There is a different inner rule for each command, plus a default
enabling the system to deal with anything which is not a valid
command.
Once a customer
process has used its single outer rule
it waits for a command to be typed and read and responds through the
appropriate inner rule. When an inner rule is double-barred, after
responding to it the process reverts to its original state where
it can use its outer rule. However when an inner rule is triple-barred,
after responding using it the process halts. In general, when a
process is responding to a set of rules, if the rule it uses
is single barred, after responding it remains in a state where it
continues to respond to those rules. If it responds to a
double-barred rule, it moves back to responding to the enclosing set
of rules, or halts if the rules were the outermost set of rules.
If the rule is triple-barred it goes back through two sets of enclosing
rules, or halts if there is only one. In other words, if there are
n bars, the process moves back through n-1 sets of
enclosing rules.
The interface is crude, with simple one-letter commands to deposit and withdraw amounts (the amount is typed in as a number following the command letter), and to print the balance and quit. A future development of Aldwych would give it a graphical user interface library, but for the purpose of these examples we are keeping to text interface.
The above example did not give a case of a single-bar rule in the
enclosing rules, but a modified version shows it. The customer
procedure above is not robust because it cannot handle the case where
a non-numerical string is typed in when a number is required as the
amount to be deposited or withdrawn. Aldwych responds in this case by
returning the constant error
rather than a number. Here
is a version of customer
which handles that by printing
an error message and asking the user to enter a figure correctly:
1 #customer(Screen,Acc)= 2 { 3 | Screen.fwrite("Command: ").readString->comm 4 <(comm) 5 { 6 comm="d" | Screen.readInt->amount 7 <(amount) 8 { 9 amount=error | 10 Screen.fwrite("Please enter a number only: ") 11 .skip 12 .readInt->amount; 13 : ||| Screen.fwrite("Deposited $">+amount).nl, 14 Acc.deposit(amount); 15 } 16 comm="w" | Screen.readInt->amount, 17 <(amount) 18 { 19 amount=error | 20 Screen.fwrite("Please enter a number only: ") 21 .skip 22 .readInt->amount; 23 : ||| <(amount) 24 ?Acc.withdraw(amount) 25 : Screen.fwrite("Withdrew $">+amount).nl; 26 : Screen.fwrite("Insufficient funds\n"); 27 } 28 comm="b" || Screen.fwrite("Balance is $">+Acc.balance).nl; 29 comm="q" |||; 30 : || Screen.fwrite("Invalid command\n"); 31 } 32 }For reference here, line numbers have been added (they do not form part of Aldwych).
Here, when a customer
process is started, it uses its
first and only rule (starting on line 3), and moves on to prompting
for and reading a command which is passed as a variable local to the inner
set of rules covering all of lines 5 to 31. If the command is the
character d
the process reacts according to the first
of these inner rules, starting on line 6. It then attempts to read
an integer which is passed in local variable amount
to an
innermost set of rules in the inner set of rules. If amount
holds error
, then the single-bar innermost rule starting on
line 9 is used. As it is a single-bar rule, computation keeps within this
innermost set of rules, skipping over the input characters
(readInt
does not advance the reading point if it
comes across a non-numeric character) and reading a new value into
amount
. If the variable amount
holds an
integer, the second innermost rule (starting one line 13) is used,
causing the amount to be deposited in Acc
by sending it a
deposit
message, and printing an acknowledgement message.
As this is a triple-barred rule, computation then returns to the outer
set of rules.
It would have been possible to write lines 6-15 as:
6 comm="d" || Screen.readInt->amount 7 <(amount) 8 { 9 amount=error | 10 Screen.fwrite("Please enter a number only: ") 11 .skip 12 .readInt->amount; 13 : || Screen.fwrite("Deposited $">+amount).nl, 14 Acc.deposit(amount); 15 }In this case, although there are only double bars on line 13, computation after this rule goes back to the outermost set of rules as the double bars on line 6 cause this level to be skipped over.
The state of the computation in an inner set of rules or a selection
statement consists only of those fields which exist in the set of rules
which is used afterwards, plus local variables explicitly passed in,
plus local variables from the lhs of the rule. That is why
amount
has to be explicitly passed in to the selection
statement on line 23. To avoid this with selection statements, the extra
bars can be put inside the selection statement (following the colons),
making lines 23-26:
23 : | 24 ?Acc.withdraw(amount) 25 :|| Screen.fwrite("Withdrew $">+amount).nl; 26 :|| Screen.fwrite("Insufficient funds\n");It is possible for a selection statement to have differing numbers of bars on its two arms. For example, the procedure
choose
below takes a list and a function on items in the list which returns a
boolean, and returns the first item in the list on which true is returned
when the function is applied. If we have:
#divby(n)[m]<==>a\eq(m//n,0);then
choose(divby(3),list)
will return the first item in
list
which is divisible by 3, or none
if none
of them are. Here is some code for choose
:
#choose(Prop,list)< { list$ ||>=none; list=head:tail | ?Prop head :|>head; : list<-tail; }The bar on the true case of the selection indicates that at this point computation halts and returns the value of
head
,
the lack of a bar on the false case indicates that list
is set to tail
and computation continues.
Using stream notation, choose
could have been written:
#choose(Prop,list)< { list$ ||>=none; list?head | ?Prop head :|>head; : }As the false case of the selection rule is empty it can be omitted, giving (also altering the layout):
#choose(Prop,list)< { list$ ||>=none; list?head | ?Prop head :|>head }The usual rule for "dangling else" applies here. It is also necessary not to omit the false case if there is a following "otherwise" colon, as in:
#choose(Prop,list)< { list?head | ?Prop head :|>head; : ||>=none }as the colon will be taken to be part of the selection statement.
This example shows that an Aldwych constant is written by the
=
symbol followed by the constant when given as a value.
Lines 9 and 19 in the customer
example showed how
a variable is tested fopr equality with a constant, using a single
=
. So amount=error
tested whether the
variable amount
holds the constant error
Compare this with amount==error
which tests whether
two variables, amount
and error
hold the
same value. On the rhs a=c
where c
is
a constant assigns the constant c
to the variable
a
, it is thus a shorthand for a<-=c