Error Handling and Debugging
This is an abridged treatment of error handling that covers key points. You will also find useful:
- Chapter 6 of Mastering Dyalog APL (Chapter E in the 2009 edition)
- Dyalog webinars on error handling
- The online documentation:
What's in a Name?
Study the following expression. It contains an error guard ::
and the primitive functions format ⍕
and execute ⍎
.
)ns Names ⍝ Create a namespace called Names
Names.va←({2 6 11::0 ⋄ ⍎(⎕UCS ⍵),'←',⍕⍵}¨⍳1E4)~0 ⍝ Valid Assignment Character Codes
-
Which three errors are trapped by the error guard?
-
⎕AV
is the list of characters which was used before Unicode was introduced. How many valid assignment characters are in⎕AV
? -
When are digits
⎕D
not allowed in names? -
Why is
9109 9054
printed to the session when the expression is run? -
Another way to find valid naming characters is to filter
⎕AV
using⎕NC
.Names.ava←⎕AV/⍨0≤⎕NC⍪⎕AV ⍝ ⎕AVU Valid Assignment Characters
How doNames.va
andNames.ava
differ?
Error Messages and Error Codes
You will have executed expressions which have resulted in some error messages by now. In fact I am certain of it because some of the previous example expressions have been deliberately written containing errors.
LENGTH ERROR
VALUE ERROR
DOMAIN ERROR
SYNTAX ERROR
If you are trapping specific errors, you might find it useful to assign error numbers to descriptive names. For example, (INDEX RANK LENGTH VALUE)←3 4 5 6
. There are a number of similar techniques which can be used, some of which are described in the Dyalog webinar Error Handling - Part 1.
-
Find the error codes of the four error messages given above.
Hint
Try:
1 2+2 3⍴⍳9 ⍝ Write some expression to generate the error ⎕←⎕EN ⍝ Inspect the Error Number of the last error ⎕←⎕EM 4 ⍝ Inspect the Error Message for that number
You can also search using APLcart
-
Write a function
ERROR
which returns error numbers for in-built messages, given a simple character vector or nested vector of character vectors as argument.ERROR 'RANK' 4 ERROR 'LENGTH' 'DOMAIN' 5 11
Note
Technically, ⎕EM
and ⎕EN
are event message and event number respectively. Most of the time you will be concerned about error events, but other types of events exist as well.
Suspend your disbelief
When an error occurs in a multi-line function, the interpreter will pause execution and show the tracer.
∇ r←x Interlace y
[1] size←1 2×⍴x
[2] even←2×⍳(⍴x)[2]
[3] r[;even]←y
[4] r[;even]-1←x
∇
⎕RL ← 73
⎕←forecast ← 10×?4 6⍴55
The state of your program is frozen in time. You can now:
- Inspect the values of variables
- Use
)SI
,⎕SI
or⎕XSI
to get a list of currently suspended functions - Inspect or use the value of
⎕DM
⎕DMX
to pass information about the error - Edit the function to fix the issue
- Either double click blank space or use Shift+Enter while the text cursor is on a blank space to turn the tracer into an editor
- Edit the code to fix the issue
- Press Esc or click the cross button ⮾ to save these changes and return to the tracer
- Continue execution by either:
- Press Enter to proceed one line at a time
- Click the trace action button to continue execution normally
- Type
→⎕LC
(go-to quad LC) to continue execution normally
Sometimes it is better to execute a function line-by-line rather than wait for an error to occur. To do this, with the text cursor on the function-calling expression, either press Ctrl+Enter or click Action → Trace from the IDE menu.
Executing, stepping into or editing a function with Enter, Ctrl+Enter and Shift+Enter can all be done while tracing through a suspended function.
Can't be helped
If you cannot fix the error immediately and would just like to return to the session, use one of:
- Press Esc to exit the current function
- Enter
→0
into the session to exit the current function and resume the calling line - Type
→
(naked branch) into the session to clear the last call on the stack - Enter
)RESET
or)SIC
into the session to clear the entire execution stack and return to the session
Gotta :Trap 'em all
The keyword :Trap ErrorNums
allows a function to specify behaviour if an error with a number in the scalar or vector ErrorNums
occurs. It can be used in conjunction with :Case N
to execute some code if the error number is N
and :Else
for the other errors. All errors are trapped with :Trap 0
.
For example:
:Trap 3 4 5 6 11
⍝ Code to execute here
:Case 3
⎕←'Index Error'
:CaseList 5 6 11
⎕←'Length, Value or Domain Error'
:Else
⎕←'Rank Error'
:EndTrap
⎕TRAP
Before :Trap
and other similar control structures were introduced, ⎕TRAP
was the way to set error trapping behaviour. Nowadays, most people will use control structures for visual clarity in their new code. However, ⎕TRAP
is still used to maintain style in existing code bases, and for certain behaviours that are not possible with :Trap
.
For example, we can alter the behaviour when an error occurs in the session:
⎕trap←5 'E' '''Silly!'''
1 2 + 3 4 5
Silly!
Note
Some errors, such as SYSTEM ERROR
, are not trappable.
Not only does the trap execute custom code when a LENGTH ERROR
occurs, but it also prevents the tracer from automatically opening when such an error occurs in a function.
∇ r←l Lerror r
[1] l+r
∇
1 2 Lerror 3 4 5
Silly!
However, those function calls are still suspended as can be seen using the state indicator:
1 2 Lerror 3 4 5
Silly!
)si
#.Lerror[1]*
#.Lerror[1]*
The 2nd element of an item in ⎕TRAP
determines what happens to the call stack (this cannot be achieved with :Trap
):
E: execute expression named in the 3rd element
C: cut back the stack to where ⎕TRAP
is localised and then execute expression named in the 3rd element
⎕TRAP←11 'C' '(↑⎕DM)'
Foo
DOMAIN ERROR
Foo[1] 1÷0
∧
0∊⍴⎕SI
1
N: next - skip this error (no 3rd element)
This is only meaningful if there is a trap 0 higher up the stack. It can be used for trapping every error except a specific one:
∇ Trap6 x;⎕TRAP
[1] ⍝ Handle all but value errors
[2] ⎕TRAP←(6 'N')(0 'E' '→ERROR')
[3] ⍎x ⍝ x contains a program to run
[4] →0
[5] ERROR:'Uh oh!'
∇
S: stop - normal error handling (no 3rd element)
This is typically used with a debugging switch:
⎕TRAP←(1+DEBUG)⊃(0 'E' 'Bad')(0 'S')
Warning
The E (execute immediately) action code is very tricky to use and best avoided. Prefer to use C, or even better, a :Trap
control structure.
Reset error trapping to default behaviour:
⎕TRAP⍴⍨←0
In general, you assign a nested vector of 3-element vectors to ⎕TRAP
, each of the form (numbers 'X' 'string')
.
Who needs to know?
Errors can occur due to a mistakenly written piece of code. However, even a perfectly valid function can trigger errors if run with input which is outside of its domain.
Note
It is best not to rely on error trapping for behaviour, in part because future extensions to the language may introduce valid behaviours in those cases.
You should test inputs explicitly using APL primitives. It can be tempting to use :Trap 0
to force alternative behaviour for unexpected inputs. However, trapping all errors should only really be done in order for your application to fail safely for the sake of end users. Best practice is to think carefully about the properties of valid inputs to your functions, and signal errors or otherwise indicate to the user that an invalid input has been attempted.
Pass it on
⎕DMX
is a system object containing information about the last occuring error. Recent versions of Dyalog can display its contents in a human-readable JSON format:
(⎕JSON⍠'Compact' 0)⎕DMX
Note
Your code should not assume that the properties in ⎕DMX
will stay the same across versions of Dyalog. See the online documentation for more information about ⎕DMX
.
The system function ⎕SIGNAL
can be used to generate errors. Errors may be signalled without a guilty expression, or custom error codes and messages may be signalled.
Monadic ⎕SIGNAL
can be used to provide custom values for ⎕DMX
.
⎕SIGNAL⊂('EN' 666)('Message' 'The Devil''s Error')('Vendor' 'Satan')
Dyadic ⎕SIGNAL
can be used to conveniently set the event message and event number. Slightly confusingly, the ⎕EM
event message is different from the error message member of the Diagnostic Message Extended: ⎕DMX.Message
.
'Yo!'⎕SIGNAL 42
⎕EM
⎕DMX.Message
Error signalling can be used to provide custom error messages, as well as to prevent the application from suspending in the tracer, which can be confusing for end users of your application who do not know APL. You might prefer that user-facing functions trap and re-signal errors, whereas utilities intended for use by APLers in the session might be left to suspend on error.
Error number ranges
Event numbers 1-99 are generated by APL.
Event numbers 1-999 can be generated by the user.
Error 0 means "any error".
Errors 1001-1008 are interrupts (e.g. break).
Error 1000 means "any interrupt".
⎕TRACE
Specify line numbers within a function or operator to have the results of expressions on those lines output to the session when executed.
Attempts to set trace controls in a locked function or operator (see ⎕LOCK
) are ignored.
The stack
The execution stack (sometimes referred to as just the stack) is the accumulation of functions either suspended or currently mid-execution. It can be inspected using )SI
, ⎕SI
and ⎕XSI
. If you attempt to run or re-run functions without having cleared the stack, then the stack simply builds up until the workspace is full.
⎕VR'Foo'
∇ Foo
[1] 1÷0
∇
Foo
DOMAIN ERROR: Divide by zero
Foo[1] 1÷0
∧
Foo
DOMAIN ERROR: Divide by zero
Foo[1] 1÷0
∧
)SI
#.Foo[1]*
#.Foo[1]*
There are a number of system variables to deal with functions on the stack:
⎕SI | The names of functions on the stack |
⎕LC | The line numbers at which each function is suspended |
⎕NSI | The namespaces prior to the call as character vectors |
⎕RSI | The namespaces prior to the call as scalar references |
⎕XSI | The full namespace paths of functions on the stack |
⎕STACK | The stack of functions and state descriptions |
To get the same diagnostic error messages printed to the session without building up the stack, you can set ⎕TRAP
to use a custom error reporting function:
∇ Error
[1] ⎕DMX.(OSError{⍵,2⌽(×≢⊃⍬⍴2⌽⍺,⊂'')/'") ("',⊃⍬⍴2⌽⊆⍺}Message{⍵,⍺,⍨': '/⍨×≢⍺}⊃⍬⍴DM,⊂'')
[2] ↑1↓⎕DMX.DM
∇
⎕TRAP←0 'C' '#.Error'
Foo
DOMAIN ERROR: Divide by zero
Foo[1] 1÷0
∧
)SI
The long, awkward expression in Error
above can be found in APLcart.
Problem set 12
Help I'm Trapped
Here we will write a simple calculator program to divide two vector arguments. When the function fails it should return to the session and leave the stack empty, but the values of ⎕DM
and ⎕EN
should change.
-
Write a program (a tradfn called
Divide
) which explicitly tests its input and signals an appropriate error for invalid arguments. -
Rewrite the program as a tradfn called
DivideQ
which uses⎕TRAP
instead of testing its arguments. -
Rewrite the program as a tradfn called
DivideC
which uses:Trap
. -
Rewrite the program as a dfn
DivideD
with error guards.
Example output:
1 2 3 Divide ,5
0.2 0.4 0.6
3 Divide ,4
RANK ERROR: Must have both vector arguments
3 Divide,4
∧
1 2 Divide 1 2 3
LENGTH ERROR: Mismatched left and right argument shapes
1 2 Divide 1 2 3
∧
1 2 Divide 1 0
DOMAIN ERROR: Divide by zero
1 2 Divide 1 0
∧
Prefix agreement operator
Leading axis agreement is a proposed extension to scalar extension which will automatically apply dyadic functions between cells when the leading axes of two arrays match.
2 3 ×_L 2 3⍴⍳6
2 4 6
12 15 18
If the leading axis lengths do not match at all, then the operator will issue an error. Write a version of the prefix agreement operator _L
which manually checks whether the argument shapes have a matching prefix, and signals a custom error otherwise.
2 3×_L 1 3⍴⍳6
LEADING AXIS MISMATCH
2 3×_L 1 3⍴⍳6
∧
⎕EN
5
Hint
If you are not sure how to write the functionality of the leading axis operator, use the definition from the APL Wiki article:
{⍺ ⍺⍺⍤(-⍺⌊⍥(≢⍴)⍵)⊢⍵}
Testing for types
Use the documentation and your own knowledge to find expressions to test for the following properties of arrays:
- Numeric arrays
- Character arrays
- Integers
- Simple arrays (non-nested)
- Uniformly nested arrays
- Scalars, vectors and matrices
- Singleton values
- Namespace references
- Namespace names as character arrays
-
In general, you assign a nested vector of 3-element vectors to
⎕TRAP
, each of the form(numbers 'X' 'string')
.In terms of its structure, what happens to the value of
⎕TRAP
if you only assign a single 3-element vector?
Can you fix it?
This scripted namespace defines a toy app to read a UTF-8 text file and convert its data to hexadecimal representation.
:Namespace app
file←'/tmp/file.txt'
∇ Main
Hex file
∇
∇ hex←Hex file;bytes;⎕TRAP
bytes←'UTF-8'∘⎕UCS¨⊃⎕NGET file 1
hex←↑{,⍉3↑(⎕D,⎕A)[16 16⊤⍵]}¨bytes
∇
:EndNamespace
The author of the function modifies it to exhibit certain error handling behaviours. Unfortunately, their code has bugs. Investigate the following scenarios and try to solve the issues.
-
The author has set up error trapping. They are aware of a potential
FILE NAME ERROR
, but have also set up a global trap in case any unexpected errors occur.:Namespace app file←'/tmp/file.txt' ∇ Main;⎕TRAP ⎕TRAP←0 'E' 'Report ⋄ →0' Hex file ∇ ∇ hex←Hex file;bytes ⎕TRAP←22 'C' '→ERROR' bytes←'UTF-8'∘⎕UCS¨⊃⎕NGET file 1 hex←↑{,⍉3↑(⎕D,⎕A)[16 16⊤⍵]}¨bytes →0 ERROR: Report ∇ ∇ Report error←↑⎕DM ⎕←'An error occurred. See app.error for more information.' ∇ :EndNamespace
Unfortunately, the function suspends with an unexpected
VALUE ERROR
.VALUE ERROR: Undefined name: ERROR →ERROR ∧
After modifying the code, the function should print to the session:
app.Main An error occurred. See app.error for more information.
The variable
app.error
should be populated:⎕←app.error FILE NAME ERROR Hex[2] bytes←'UTF-8'∘⎕UCS¨⊃⎕NGET file 1 ∧
-
Now that the file name error is handled, they want to test the application using a file. Paste the following into a text editor and save it somewhere. Update
app.file
to point to the correct location.sample text
Running
app.Main
reveals either 1 or 2 more bugs:- Running the function now results in an
INDEX ERROR
. - The global trap did not catch the
INDEX ERROR
.
Fix the remaining bugs. The application should successfully convert the file now:
app.Main 73 61 6D 70 6C 65 20 74 65 78 74
- Running the function now results in an
-
Finally, the author decides it would be more useful if
app.error
contained more information about the error, and also that theReport
function should display this directly in the session as well.:Namespace app file←'/tmp/file.txt' ∇ Main;⎕TRAP ⎕TRAP←0 'E' 'Report {⍵(⍎⍵)}¨⎕NL¯2 ⋄ →0' Hex file ∇ ∇ hex←Hex file;bytes ⎕TRAP←22 'C' '→ERROR' bytes←'UTF-8'∘⎕UCS¨⊃⎕NGET file 1 hex←↑{,⍉3↑(⎕D,⎕A)[16 16⊤⍵]}¨bytes →0 ERROR: Report⊂'file' file ∇ ∇ Report names_values error←⊂↑⎕DM error,←⊂↑names_values ⎕←'An error occurred. Error information in app.error:' ⎕←error ∇ :EndNamespace
- Turn
]box -fns=on
-
Reinstate the
FILE NAME ERROR
and runapp.Main
again.app.Main An error occurred. Error information in app.error: ┌───────────────────────────────────────┬────────────────────┐ │FILE NAME ERROR │┌────┬─────────────┐│ │Hex[2] bytes←'UTF-8'∘⎕UCS¨⊃⎕NGET file 1││file│/tmp/file.txt││ │ ∧ │└────┴─────────────┘│ └───────────────────────────────────────┴────────────────────┘
-
Reinstate the
INDEX ERROR
and runapp.Main
again at least twice.- What do you notice about
app.error
? - Try to solve this issue. There is more than one valid solution.
- What do you notice about
- Turn