A Girl’s Best Friend
Vector 19:2, page 109
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 109
0. introduction¶
No significant domain algebra this time, just a suggestion for expanding the current class of statement separators by the addition of two more items, allowing better control over exception handling.
1. the present situation¶
1.1 APL functions consist of a sequence of lines. In addition, some implementations allows braces to be used to group lines into blocks.
Each line may have 0, 1 or many separate executable statements, followed by an optional trailing comment.
If a line has more than one statement, the statements are separated by a diamond.
1.2 Actually, the description of diamond as a "statement separator" is a misnomer. Other languages may describe the comma as a separator, but in APL we know it as a primitive function which joins, not separates, two arrays.
Like a number of other such functions, the diamond is really a constructor, which joins up statements into lines. Or, to put it another way, it is a function which takes executable statements as arguments, and delivers another executable.
(Contrast this with the use of use of ⊣ and ⊢ ("lev" and "dex") which achieve a similar effect by operating in the data domain. This only works, however, if every subexpression delivers a result lying within the data domain.)
2. the present mechanism¶
2.1 The only issue with these function/line/statement things is how to handle side-effects, the most obvious of these being embedded assignments. We need more flexibility in this area if we want to improve exception handling, and the key concepts (borrowed from the database world) are those of "commit" and "rollback".
When an expression like
is executed, the interpretation proceeds from right to left. The rightmost occurrence of N picks up the current value of N, and the leftmost occurrence of N receives the updated value. This means that in a statement like
the value of N will have been permanently updated if the expression fails thus:
2.2 To borrow an idea from Z, we can use primes to indicate updated values. If function FOO updates a global variable X, we specify pre-conditions on that variable by referring to X, while post-conditions refer to X′. Thus we can clarify our earlier example, by writing
and the old idiom
becomes
2.3 Phil Abrams once proposed that values should be bound at the start of execution of a line — this may have been in the days before diamond statement separators — which would have given us the sequence
where RECIP takes the value of the reciprocal of the value of N before updating.
In the event, the idea was never adopted, but it still has merit. In particular, it would allow us to rerun a bad line in the same environment, while debugging.
Languages like SQL have long had to cope with complicated updates which may ultimately have to be abandoned. The update is applied tentatively. If at any point the update fails to meet certain specified constraints, the transaction is rolled back, and the database left in its original state. If the update is completed satisfactorily, it is then committed explicitly, and the new data becomes visible to other users. This, of course, requires transaction_start and transaction_end markers. In the absence of such markers, updates are committed immediately, by default.
3. an improved mechanism¶
3.1 It is clear that current practice in APL is to commit updates immediately, by default, with no rollback option.
It is, however, helpful to think of all side-effects as tentative, until the end of the statement is reached. If the end of the statement is marked by a diamond or end-of-line, the side-effects are committed. This does not affect the current definition for ⋄, which we would retain as the default semantics.
We would also introduce two new connectors, which are for the moment written as <&> and <•>.
At their simplest:
<&> would defer commitment until the end of the following statement;
<•> would trigger a rollback in the event of error in the left-hand statement, clear the error flag and execute the right-hand statement (which would presumably specify some sort of recovery routine) in the original environment. Otherwise, the interpreter would skip over the right hand statement to the next statement connector before deciding whether to commit outstanding updates.
3.2 For example,
would recover from an attempt to take the reciprocal of zero, but in doing so, it would also have rolled back the decrement of N, so that the more complete solution of
might be preferred.
Alternatively, the statement
would ensure that the decrement is always committed, and the reciprocal operation is always protected.
3.3 The sequence
defers commitment until THE_OTHER has completed execution. If this execution is error-free, the RECOVER statement is skipped. In the event of any error in the protected section, the interpreter rolls the environment back to the beginning of the line, undoing all updates made by THIS and THAT, before finally invoking RECOVER in the restored environment.
3.4 The sequence
defers commitment until THIS and THAT have completed execution. If this execution is error-free, the RECOVER statement is skipped, and interpretation continues with THE_OTHER. In the event of any error in the protected section, the interpreter rolls the environment back to the beginning of the line, undoing all updates made by THIS and THAT, before invoking first RECOVER and then THE_OTHER in the restored environment.
3.5 In the event that THIS executes without error, the sequence
would skip forward over RECOVERY1 and RECOVERY2, and execute THAT. In effect, the routine RECOVERY2 protects RECOVERY1.
3.6 If we are allowed to use braces to group lines together, the sequence
could (given some mechanism for passing back error flags) be made protect the whole block.
4. closing remarks¶
4.1 This is not the last word in error recovery, but it is an improvement.
To extend "error recovery" facilities into "event handling" facilities, the user program needs some means to indicate that it wishes either to abandon execution of the current statement, or to leave the current block with the error flag set, and ⎕signal or something similar would do the job very nicely.
4.2 Instead of returning to the most recently committed state, the programmer may prefer to return to some earlier environment. If that earlier environment falls within the current block then a branch command as the recovery statement may be all that is required.
If the desired earlier environment is external to the current block, then things get messy. It is a reasonable requirement, frequently needed in response to a "Quit" or "Cancel" command, but sometimes it can only be implemented using ugly constructs which trap error code 666 (or whatever) and pass back the error to the calling function, and so on, until the required environment is reached. This is all part of a larger problem, which may be tackled in due course.
4.3 The use of the tri-graphs, <&> and <•>, is highly unsatisfactory, but until better symbols suggest themselves, the chosen ones have the merit of suggesting the "and" and "or" element of the connectors’ semantics.
4.4 A proper definition of the proposed new connectors is not itself a difficult task, but requires the definition of a new domain, 𝔼, of executable statements.
Despite their central importance, executable statements are not very exciting objects: we define them (recursively), we combine them and we execute them.
The ways in which we define statements are defined by the syntax, the way they are executed is defined by the execution model, which only leaves the ways in which they are combined.
All diamond operators are elements of 𝔼↤⟨𝔼;𝔼⟩. The operators ⋄ and <&> both deliver domain products, their results being members of (𝔼⊗𝔼) . The operator <•> , on the other hand, delivers a domain sum, the result being an element of (𝔼⊕𝔼) , but, since the construct
is radically different from
it has the added complication that this particular domain sum is not commutative.
4.5 Anybody interested in pursuing this topic from the theoretical point of view might do worse than start with
Dana Scott (1971), The Lattice of Flow Diagrams
(in) E Engeler [ed], Symposium on Semantics of Algorithmic Languages
Springer-Verlag, Berlin – Heidelberg
which uses flow diagrams as a simple example to demonstrate the techniques of "approximation" and limit domains.