Error Trapping Tutorial for APL*PLUS
Vector 6:1, page 91
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 91
Background¶
The purpose of this tutorial is to review the error trapping tools available to users of APL*PLUS systems. The facilities described are standard throughout the whole family of APL*PLUS interpreters, and operate in the same way on all APL*PLUS systems.
What happens when an error occurs? Let us take as our example the following expression, which we know will result in an error:
Although it looks fairly straightforward, the system actually does quite a bit of work to get to that result. At this point we should distinguish between an error which is ‘signalled’ and an error which is ‘reported’. When APL encounters an error, we say that an error is signalled. This does not necessarily mean that the error will be reported to the user, although the system’s default behaviour is to do so, and in the above example this is exactly what happens.
In this tutorial we shall see how you can override the system’s default behaviour by trapping signalled errors before they are reported. This is often called error trapping. What you do once you have trapped the error is entirely up to you of course; error handling is a much wider subject, beyond the scope of this article, as they say.
Checking the Diagnostic Message¶
If we are to stand any chance of handling an error we will need to know what error would have been reported by the system. This information can be produced by looking at the result of ⎕DM, a niladic system function which records the last Diagnostic Message recorded in the workspace:
The diagnostic message produced above consists of three parts; the kind of error, the line that caused the error, and a caret that indicates the point in the line at which the error was detected. You have to look quite carefully to see that typing ⎕DM did not itself cause an error, but just displayed the diagnostic message. The diagnostic message is a newline delimited character vector; we can check this by looking at its shape:
Remember that the value produced by ⎕DM will be different if a new error occurs.
Trapping the Original Error¶
So ⎕DM can be used to handle our error, but how can we trap it in the first place? To do this we use the system variable ⎕ELX, which contains the Error Latent Expression. As you might guess from its name, the error latent expression is a character vector which is executed by the system when an error is signalled. Because it is a system variable we can assign a value to it, for example:
Let us see what happens when we generate an error:
The system’s default error trapping behaviour was overridden, and we caused our own message to be displayed. Not a very helpful error message you might think.
Of course, the system has a default value for ⎕ELX, so that if you do not set it errors can still be reported. Can you see what that default value is?
Let us find out:
The default value for ⎕ELX is the character vector '⎕DM'. So the system default behaviour is merely to display the diagnostic message. Notice that after the ⎕ELX has been executed you are left in immediate execution mode.
We can embellish our error message by refining the value of ⎕ELX, which can contain any APL expression. For example:
Let’s try it out:
Remember that the diagnostic message contains the text of the signalled error which the system would have displayed:
Another approach is to display the message and then execute an error processing routine. The purpose of this routine might be to log the error on a file somewhere, and inform the system steward. A suitable ⎕ELX might be:
What do you think would happen if an error occurred in the ERROR∆PROCESS routine itself?
A good way of protecting against infinite loops in error handling code is always carefully to reset ⎕ELX as soon as it is executed, for example:
Because ⎕ELX is a system variable it can be included in the locals list of a defined function. In this way the scope of its effect can be controlled. When an error is signalled, it is the most local value of ⎕ELX which is executed.
We have seen how ⎕ELX and ⎕DM can be used to control the trapping of APL errors in a general way.
Signalling Your Own Errors¶
Another useful tool is the system function ⎕ERROR, which allows you to signal your own errors. Why should you want to signal your own errors, when you probably get enough already?
As an example consider the function SQRT which takes the square root of a number:
∇ Z←SQRT W
[1] ⍝ <Z> is the square root of <W>
[2]
[3] ⎕ERROR(1∊W<0)/'ARGUMENT NEGATIVE'
[4] Z←W*0.5
∇
If <W> is not negative, then the argument to ⎕ERROR is an empty vector, and nothing happens; execution continues on line 4. If however, <W> is negative, then ⎕ERROR will cause an error to be signalled. The error message signalled is our own tailored message 'ARGUMENT NEGATIVE'. Let’s see it in action:
Notice that the error was signalled in the environment that called the function SQRT, not within the function SQRT itself; we have made SQRT behave like a primitive APL function in this respect. We can confirm this by checking the )SI stack:
The )SI stack is still clear. The ⎕DM contains our own error message, exactly as signalled:
Of course there are other errors which could occur with SQRT, for example we might pass it character data. Can you see how ⎕ERROR can be used to pass any APL error back to the calling environment?
The following version of SQRT retains our own tailored message, and additionally passes all other errors to its caller:
∇ Z←SQRT W;⎕ELX
[1] ⍝ <Z> is the square root of <W>
[2]
[3] ⎕ELX←'⎕ERROR (∧\⎕DM≠⎕TCNL)/⎕DM'
[4] ⎕ERROR(1∊W<0)/'ARGUMENT NEGATIVE'
[5] Z←W*0.5
∇
SQRT 4
2
SQRT ¯4
ARGUMENT NEGATIVE
SQRT ¯4
^
SQRT 'HELLO'
DOMAIN ERROR
SQRT 'HELLO'
^
Line 3 is a common APL*PLUS error trapping idiom; it causes any error which occurs within SQRT to be signalled in the calling environment. The expression (∧\⎕DM≠⎕TCNL)/⎕DM extracts the name of the error from the diagnostic message which is produced when the error is signalled.
Notice that ⎕ELX has been localised in order to ensure that its effect is restricted to SQRT, and any of its sub-functions. The following question is left for the reader: What do you think happens if SQRT calls a sub-function in which ⎕ELX is not localised or assigned, and an error occurs within it?
To recap; the system names ⎕ELX, ⎕DM and ⎕ERROR are all you need to successfully trap and handle APL errors in APL*PLUS systems. Error trapping is powerful, and open to misuse and abuse. A wide range of error handling tools and appropriate techniques can be developed from these primitives, topics which have not been covered here.