Error Trapping Tutorial in Dyalog APL
Vector 6:1, page 100
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 100
Background¶
We often give courses to people who taught themselves APL and now have several years’ programming experience to their credit, and we have found that a surprising number of them have not discovered some of the “non-language” features available in most APLs, in particular “error trapping” or “event handling”.
The purpose of this article is to show some of the ways in which the ideas of error trapping can be used to great effect to change the flow of control in a system.
Introduction to Error Trapping¶
Most APLs have error trapping facilities in one form or another, but this article discusses the facilities available to a Dyalog APL programmer.
First, we must have an idea of what is meant by error trapping. We are all used to entering some duff APL code and seeing a (sometimes) rather obscure, esoteric error message echoed back:
Now, these sorts of error messages are fine for us clever APL programmers, but meaningless to most of our users. We need to find a way to bypass the default action of APL, so that we can take an action of our own.
Every error message reported by Dyalog APL has a corresponding error number (see the Appendix). Many of these error numbers plus messages are common across all versions of APL. The code for DOMAIN ERROR is 11, whilst LENGTH ERROR has code 5.
Dyalog APL provides system functions and variables that allow you to find out this information without looking it up in the table:
10÷0
DOMAIN ERROR
10÷0
^
⎕EN ⍝ ERROR NO FOR LAST ERROR
11
⎕EM 11 ⍝ ERROR MESSAGE FOR CODE 11
DOMAIN ERROR
⎕DM ⍝ DIAGNOSTIC MESSAGE
DOMAIN ERROR 10÷0 ^
⍝ 3-ELEMENT NESTED VECTOR OF
⍝ MESSAGE, NUMBER AND CARAT
DISPLAY ⎕DM ⍝ DISPLAY TO SHOW STRUCTURE
.→------------------------------.
|.→-----------..→--------..→----.|
||DOMAIN ERROR|| 10÷0|| ^||
|'------------''---------''-----'|
'∊-------------------------------'
↑⎕DM ⍝ MIX (↑) OF VECTOR GIVES A
DOMAIN ERROR ⍝ SIMPLE MATRIX, WHICH IS
10÷0 ⍝ THE SAME AS THE ERROR MSG
^ ⍝ DISPLAYED BY APL
We also need a facility to tell APL “whenever error 11 occurs, instead of doing what you normally do, take this action instead”. In Dyalog APL, we use the system variable ⎕TRAP, which can be assigned a nested vector of trap specifications. Each trap specification is itself a nested vector, of length 3, with each element defined as:
- list of error number(s): the error numbers we are interested in.
- action code: Either ‘E’ (Execute) or ‘C’ (Cut Back). There are others, but they are seldom used.
- action to be taken: APL expression, usually a branch statement or a call to an APL function.
Setting a Simple Trap¶
So a single trap specification may be set up as:
and a multiple trap specification may be set up as:
The action code E tells APL that you want your action to be taken in the function in which the error occurred, whereas the code C indicates that you want your action to be taken in the function where the ⎕TRAP was set; if necessary, APL must first travel back up the SI stack (cut-back) until it reaches that function. These action codes are best illustrated by example.
Let’s try setting a ⎕TRAP on DOMAIN ERROR:
When we enter:
APL executes the expression, and notes that it causes an error number 11. Before issuing the standard error, it scans its ⎕TRAP table, to see if you were interested enough in that error to set a trap; you were, so APL executes the action specified by you:
Let’s reset our ⎕TRAP:
and write a function to take the place of the intrinsic ÷ …
Then run it:
Let’s edit our function and include a localised ⎕TRAP:
∇ R←A DIV B;⎕TRAP
[1] ⍝ SET THE TRAP
[2] ⎕TRAP←11 'E' '→ERR1'
[3] ⍝ DO THE WORK; IF IT RESULTS IN ERROR 11, EXECUTE THE TRAP
[4] R←A÷B
[5] ⍝ ALL OK IF WE GOT TO HERE, SO EXIT
[6] →0
[7] ⍝ WILL GET HERE ONLY IF ERROR 11 OCCURRED
[8] ERR1:'PLEASE GIVE A NON-ZERO RIGHT ARGUMENT'
∇
Running the function with good and bad arguments has the desired effect:
⎕TRAP is a variable like any other, and since it is localised in DIV, it is only effective in DIV and any other functions that may be called by DIV. So ….
still gives an error, since there is no trap set in the global environment.
What happens to our function if we run it with other duff arguments:
Here is an error that we have taken no account of. Change DIV to take this new error into account:
∇ R←A DIV B;⎕TRAP
[1] ⍝ SET THE TRAP
[2] ⎕TRAP←(11 'E' '→ERR1')(5 'E' '→ERR2')
[3] ⍝ DO THE WORK; IF IT RESULTS IN ERROR 11, EXECUTE THE TRAP
[4] R←A÷B
[5] ⍝ ALL OK IF WE GOT TO HERE, SO EXIT
[6] →0
[7] ⍝ WILL GET HERE ONLY IF ERROR 11 OCCURRED
[8] ERR1:'PLEASE GIVE A NON-ZERO RIGHT ARGUMENT' ⋄ →0
[9] ⍝ WILL GET TO HERE ONLY IF ERROR 5 OCCURRED
[10] ERR2:'ARGUMENTS MUST BE SAME LENGTH'
∇
)RESET
1 2 3 DIV 4 5
ARGUMENTS MUST BE THE SAME LENGTH
But here’s another problem that we didn’t think of:
Often when we’re writing a system, we can’t think of everything that may go wrong ahead of time; so we need a way of catching “everything else that I may not have thought of”. The error number used for “everything else” is zero:
)RESET
⎕TRAP ← 0 'E' ' ''INVALID ARGUMENTS'' ' ⍝ SET GLOBAL TRAP
(2 3⍴⍳6) DIV (2 3 4⍴⍳24) ⍝ AND RUN FUNCTION
INVALID ARGUMENTS
In this case, when APL executed line 4 of our function DIV, it encountered an error number 4 (RANK ERROR). It searched the local trap table, found nothing relating to error 4, so searched further up the stack to see if the error was mentioned anywhere else. It found an entry with an associated Execute code, so executed the appropriate action AT THE POINT THAT THE ERROR OCCURRED. Let’s see what’s in the stack:
So although our action has been taken, execution has stopped where it normally would after a RANK ERROR.
Beware of Global Traps!¶
We must be careful when we set global traps; let’s call the non-existent function BUG whenever we get an unexpected error:
Nothing happens, since APL traps a RANK ERROR on line 4 of DIV, so executes the trap statement, which causes a VALUE ERROR, which activates the trap action, which causes a VALUE ERROR, which …. etc., etc. If we had also chosen to trap on 1000 (ALL INTERRUPTS), then we’d be in trouble!
Let’s define a function BUG:
∇ BUG
[1] ⍝ CALLED WHENEVER THERE IS AN UNEXPECTED ERROR
[2] '*** UNEXPECTED ERROR OCCURRED IN FUNCTION',⊃1↓⎕SI
[3] '*** PLEASE CALL YOUR SYSTEM ADMINISTRATION'
[4] '*** WORKSPACE SAVED AS BUG.',⊃1↓⎕SI
[5] ⍝ TIDY UP ... RESET ⎕LX, UNTIE FILES ... ETC
[6] ⎕SAVE 'BUG.',⊃1↓⎕SI
[7] '*** LOGGING YOU OFF THE SYSTEM'
[8] ⎕OFF
∇
Now, whenever we run our system and an unexpected error occurs, our BUG function will be called.
10 DIV 0
PLEASE GIVE NON-ZERO RIGHT ARGUMENT
(2 3⍴⍳6) DIV (2 3 4⍴⍳12)
*** UNEXPECTED ERROR OCCURRED IN FUNCTION DIV
*** PLEASE CALL YOUR SYSTEM ADMINISTRATOR
*** WORKSPACE SAVED AS BUG.DIV
*** LOGGING YOU OFF THE SYSTEM
The system administrator can then load BUG.DIV, look at the SI stack, discover the problem, and fix it.
In many cases, you can of course achieve the same effect of a trap by using APL code to detect the problem before it happens. Consider the function TIE∆FILE, which checks to see if a file already exists before it tries to access it:
∇ R←TIE∆FILE FILE;FILES
[1] ⍝ TIE FILE FILE WITH NEXT AVAILABLE TIE NUMBER
[2] FILES←⎕FLIB 'MYDIR' ⍝ ALL FILES IN MY DIRECTORY
[3] FILES←DBR¨↓FILES ⍝ REMOVE TRAILING BLANKS
[4] →ERR×⍳~(⊂FILE)∊FILES ⍝ REQD FILE IN LIST?
[5] FILE ⎕FTIE R←1+⌈/0,⎕FNUMS ⍝ TIE FILE WITH NEXT NUMBER
[6] →0 ⍝ ... AND EXIT
[7] ERR:R←'FILE DOES NOT EXIST' ⍝ ERROR MESSAGE
∇
This function executes the same code whether the file name is right or wrong, and it could take a while to get all the file names in your directory. It would be neater and more efficient to take action ONLY when the file name is wrong:
∇R←TIE∆FILE FILE;⎕TRAP
[1] ⍝ TIE FILE FILE WITH NEXT AVAILABLE TIE NUMBER
[2] ⎕TRAP←22 'E' '→ERR' ⍝ SET TRAP
[3] FILE ⎕FTIE R←1+⌈/0,FNUMS ⍝ TIE FILE WITH NEXT NUMBER
[4] →0 ⍝ ... AND EXIT IF OK
[5] ERR:R←'FILE DOES NOT EXIST' ⍝ ERROR MESSAGE
∇
Cutting Back the Stack¶
Let us consider the effect of using Cut-Back instead of Execute. Consider the system illustrated below, in which the function REPORT gives the user the option of 4 reports to be generated:
where REPORT looks something like this:
∇ REPORT;OPTIONS;OPTION;⎕TRAP
[1] ⍝ DRIVER FUNCTIONS FOR REPORT SUB-SYSTEM. IF AN
[2] ⍝ UNEXPECTED ERROR OCCURS, TAKE ACTION IN FUNCTION WHERE
[3] ⍝ ERROR OCCURRED
[4] ⎕TRAP←0 'E' 'BUG' ⍝ SET GLOBAL TRAP
[5] OPTIONS←'REP1' 'REP2' 'REP3' 'REP4' ⍝ AVAILABLE OPTIONS
[6] LOOP:→END×⍳0=⍴OPTION←MENU OPTIONS ⍝ ASK USER TO CHOOSE
[7] ⍎OPTION ⍝ EXECUTE RELEVANT FUNCTION
[8] →LOOP ⍝ REPEAT UNTIL EXIT
[9] END: ⍝ NOW END
∇
Suppose the user chooses REP3, and an unexpected error occurs in DIV.
The good news is that the system administrator gets a snapshot copy of the workspace that he can play with:
)LOAD BUG.DIV ⍝ LOAD WORKSPACE
SAVED ......
)SI ⍝ WHERE DID ERROR OCCUR?
DIV[4]*
REP3[6]
⍎
REPORT[7]
↑⎕DM ⍝ WHAT HAPPENED?
RANK ERROR
DIV[4] R←A÷B
^
∇ ⍝ EDIT FUNCTION ON TOP OF
STACK
[0] R←A DIV B
.........
The bad news is, our user is locked out of the whole system, even though it may only be REP3 that has a problem. We can get around this by making use of the CUT-BACK action code:
∇ REPORT;OPTIONS;OPTION;⎕TRAP
[1] ⍝ DRIVER FUNCTIONS FOR REPORT SUB-SYSTEM. IF AN
[2] ⍝ UNEXPECTED ERROR OCCURS, CUT THE STACK BACK TO THIS
[3] ⍝ FUNCTION, THEN TAKE ACTION
[4] ⎕TRAP←0 'C' '→ERR' ⍝ SET GLOBAL TRAP
[5] OPTIONS←'REP1' 'REP2' 'REP3' 'REP4' ⍝ AVAILABLE OPTIONS
[6] LOOP:→END×⍳0=⍴OPTION←MENU OPTIONS ⍝ ASK USER TO CHOOSE
[7] ⍎OPTION ⍝ EXECUTE RELEVANT FUNCTION
[8] →LOOP ⍝ REPEAT UNTIL EXIT
[9] ERR:MESSAGE'UNEXPECTED ERROR IN',OPTION ⍝ TELL USER
[10] MESSAGE:'REMOVING FROM LIST' ⍝ ... WHAT'S HAPPENING
[11] OPTIONS← OPTIONS ~⊂OPTION ⍝ REMOVE OPTION FROM LIST
[12] →LOOP ⍝ ... AND REPEAT
[13] END: ⍝ END
∇
Suppose the user runs this version of REPORT and chooses REP3. When the unexpected error occurs in DIV, APL will check its trap specifications, and see that the relevant trap was set in REPORT with a cut-back code. APL therefore cuts back the stack to the function in which the trap was set, THEN takes the specified action. Looking at the SI stack above, we can see that APL must jump out of DIV, then REP3, then ⍎, to return to line 7 of REPORT; THEN it takes the specified action.
User-defined Events¶
It would be useful to be able to employ this idea of cutting back the stack and taking an alternative route through the code, when a condition other than an APL error occurs. To achieve this, we must be able to trap on events other than APL errors, and we must be able to define these events to APL. We do the former by using event codes in the range 500 to 999, and the latter by using ⎕SIGNAL.
Consider our system; ideally, when an unexpected error occurs, we want to save a snapshot copy of our workspace (execute BUG in place), then immediately jump back to REPORT (cutback on an event, not an APL error) and reduce our options. We can achieve this by changing our functions a little, and using ⎕SIGNAL:
∇ REPORT;OPTIONS;OPTION;⎕TRAP
[1] ⍝ DRIVER FUNCTIONS FOR REPORT SUB-SYSTEM. IF AN
[2] ⍝ UNEXPECTED ERROR OCCURS, MAKE A SNAPSHOT COPY OF THE
[3] ⍝ WORKSPACE THEN CUTBACK THE STACK TO THIS FUNCTION
[4] ⎕TRAP←(0 'E' 'BUG')(500 'C' '→ERR') ⍝ SET GLOBAL TRAP
[5] OPTIONS←'REP1' 'REP2' 'REP3' 'REP4' ⍝ AVAILABLE OPTIONS
[6] LOOP:→END×⍳0=⍴OPTION←MENU OPTIONS ⍝ ASK USER TO CHOOSE
[7] ⍎OPTION ⍝ EXECUTE RELEVANT FUNCTION
[8] →LOOP ⍝ REPEAT UNTIL EXIT
[9] ERR:MESSAGE'UNEXPECTED ERROR IN',OPTION ⍝ TELL USER
[10] MESSAGE:'REMOVING FROM LIST' ⍝ ... WHAT'S HAPPENING
[11] OPTIONS← OPTIONS ~⊂OPTION ⍝ REMOVE OPTION FROM LIST
[12] →LOOP ⍝ AND REPEAT
[13] END: ⍝ END
∇
∇ BUG
[1] ⍝ CALLED WHENEVER THERE IS AN UNEXPECTED ERROR
[2] '*** UNEXPECTED ERROR OCCURRED IN FUNCTION',⊃1↓⎕SI
[3] '*** PLEASE CALL YOUR SYSTEM ADMINISTRATOR'
[4] '*** WORKSPACE SAVED AS BUG.',⊃1↓⎕SI
[5] ⍝ TIDY UP .... RESET ⎕LX, UNTIE FILES ... ETC
[6] ⎕SAVE 'BUG.',⊃1↓⎕SI
[7] '*** RETURNING YOU TO DRIVER FUNCTION FOR RESELECTION'
[8] ⎕SIGNAL 500
∇
Now when the unexpected error occurs, the first trap specification catches it, and the BUG function is executed in place. Instead of logging the user off as before, an event 500 is signalled to APL. APL checks its trap specifications, sees that 500 has been set in REPORT as a cut-back, so cuts back to REPORT before branching to ERR.
Event handling, which employs a combination of all the system functions and variables described, allows us dynamically to alter the flow of control through our system, as well as to handle errors gracefully. It is a very powerful facility, which is simple to use but is often neglected.
Appendix: The Full Error-code Table¶
| Code | Event |
|---|---|
| 0 | Any event in range 1-999 |
| 1 | WS FULL |
| 2 | SYNTAX ERROR |
| 3 | INDEX ERROR |
| 4 | RANK ERROR |
| 5 | LENGTH ERROR |
| 6 | VALUE ERROR |
| 7 | FORMAT ERROR |
| 10 | LIMIT ERROR |
| 11 | DOMAIN ERROR |
| 12 | HOLD ERROR |
| 15 | LST FULL |
| 16 | NONCE ERROR |
| 18 | FILE TIE ERROR |
| 19 | FILE ACCESS ERROR |
| 20 | FILE INDEX ERROR |
| 22 | FILE NAME ERROR |
| 23 | FILE DAMAGED |
| 24 | FILE TIED |
| 26 | FILE SYSTEM ERROR |
| 28 | FILE SYSTEM NOT AVAILABLE |
| 30 | FILE SYSTEM TIES USED UP |
| 31 | FILE TIE QUOTA USED UP |
| 32 | FILE NAME QUOTA USED UP |
| 34 | FILE SYSTEM NO SPACE |
| 38 | FILE COMPONENT DAMAGED |
| 72 | NO PIPES |
| 73 | LOST SLAVE |
| 76 | PROCESSOR TABLE FULL |
| 84 | TRAP ERROR |
| 200 - 499 | Reserved for distributed auxiliary processors |
| 500 - 999 | User-defined events |
| 1000 | Any event in range 1001-1006 |
| 1001 | Stop vector |
| 1002 | Weak interrupt |
| 1003 | INTERRUPT |
| 1004 | INPUT INTERRUPT |
| 1005 | EOF INTERRUPT |
| 1006 | TIMEOUT |