Error handling
Error handling#
Controlling complexity is the essence of computer programming. –Brian Kernighan
Other resources on the topic:
Dyalog docs: Error guards, Error codes, Signal event
By now you will have encountered a range of errors where Dyalog takes objection to something you wrote, like for example
vec ← ⍳10
50⊃vec ⍝ INDEX ERROR
INDEX ERROR
50⊃vec ⍝ INDEX ERROR
∧
If you’re used to a Java or Python stack trace, the errors thrown by Dyalog will appear spartan, but after a bit of experience, they’re actually blissfully spartan, not unlike APL itself. As an aside, the K language is even more brutal in its simplicity here.
So how do you deal with errors? There is no try-catch-throw
exception mechanism to draw on. What we have instead is the concept of an error guard:
]dinput
pick50 ← {
3::'out of range' ⍝ Error guard, catching INDEX ERROR
50⊃⍵
}
pick50 ⍳10
An error guard takes an array of error numbers to the left, and an expression to be returned to the right, separated by two colons. Simple, right? Wrong.
Here’s a rather contrived function that prepends a 1 to its left argument and then multiplies it with the inverse of its right argument. We want to protect against division by zero, in which case we just want to return the 1 prepended to the left argument, like so:
]dinput
f ← {
nums←1 ⍺
0=⍵:nums
nums×÷⍵
}
4 f 0
4 f 2
This seems like a job for an error guard. Division by zero in this case will throw a DOMAIN ERROR
, which has a code of 11.
Try to predict what happens before revealing the actual result:
]dinput
fbug ← { ⍝ Can you spot the bug?
11::nums
nums←1 ⍺
nums×÷⍵
}
4 fbug 0
VALUE ERROR: Undefined name: nums
fbug[1] 11::nums
∧
What happens is that the local environment is unwound before the error-guard’s body is evaluated. To get the behavior we wanted we’d need to do this:
]dinput
fnew ← { ⍝ Now correct
nums←1 ⍺
11::nums
nums×÷⍵
}
4 fnew 0
The other subtlety is that following the setting of an error guard, subsequent calls disable tail-call optimization:
]dinput
g ← {
3::'out of range' ⍝ Error guard, catching INDEX ERROR
h ⍺⊃⍵ ⍝ No longer a tail call, due to presense of error guard
}
This can be worked around by localizing the error guard in its own dfn:
]dinput
g ← {
val ← ⍺ {3::'out of range' ⋄ ⍺⊃⍵} ⍵
h val ⍝ Tail call
}
The mapping of numeric error code to error message can be found in Dyalog’s documentation, referenced above, or via the system function ⎕EM
:
⎕EM⍳10 ⍝ ...and the rest
Error guards can also be stacked if you want to take different actions for different kinds of errors:
]dinput
g ← {
3::'handle index error'
4::'handle rank error'
11::'handle domain error'
⍝ error-prone function body here
}
or if you want a catch-all for the errors you think you might encounter, the guard takes a vector to the left:
]dinput
g ← {
3 4 11::'it all went pear-shaped in some way'
⍝ error-prone function body here
}
You can also (sort of) throw “exceptions”. You may have noticed the definition for assert
I’ve used here and there already:
assert ← {⍺←'assertion failure' ⋄ 0∊⍵:⍺ ⎕SIGNAL 8 ⋄ shy←0}
assert 0=1
assertion failure
assert 0=1
∧
The ⎕SIGNAL
ambivalent system function throws an error of the number given by its right argument, presenting as the corresponding message from the vector given by ⎕EM
if no left argument is given, or by the left argument if given, as is the case in the assert
function above. In other words, we could for example claim that the “workspace is full” by throwing error 1, somewhat duplicitously:
⎕SIGNAL 1
WS FULL
⎕SIGNAL 1
∧
In the assert
function we make use of an error code that isn’t used by Dyalog normally, error 8:
⎕SIGNAL 8
ERROR 8
⎕SIGNAL 8
∧