Error handling

Controlling complexity is the essence of computer programming. –Brian Kernighan

Other resources on the topic:

By now you will have encountered a range of errors where Dyalog takes objection to something you wrote, like for example

vec  10
50vec      ⍝ 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
out of range

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  {
    nums1 
    0=⍵:nums
    nums×÷
}
4 f 0
4 f 2
1 4
0.5 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
    nums1 
    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
    nums1 
    11::nums
    nums×÷
}
4 fnew 0
1 4

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:

⎕EM10 ⍝ ...and the rest
┌───────┬────────────┬───────────┬──────────┬────────────┬───────────┬────────────┬───────┬───────┬───────────┐ │WS FULL│SYNTAX ERROR│INDEX ERROR│RANK ERROR│LENGTH ERROR│VALUE ERROR│FORMAT ERROR│ERROR 8│ERROR 9│LIMIT ERROR│ └───────┴────────────┴───────────┴──────────┴────────────┴───────────┴────────────┴───────┴───────┴───────────┘

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  shy0}
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
      ∧