prev up next

8) Nested rule selection

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

prev up next