Error-Guards

An error-guard is (an expression that evaluates to) a vector of error numbers (see APL Error Messages), followed by the digraph: ::, followed by an expression, the body of the guard, to be evaluated as the result of the function. For example:

      11 5 :: ⍵×0 ⍝ Trap DOMAIN and LENGTH errors.

In common with :Trap and ⎕TRAP, error numbers 0 and 1000 are catch-alls for synchronous errors and interrupts respectively.

When an error is generated, the system searches dynamically upwards and outwards for an error-guard that matches the error. If one is found, the execution environment is unwound to its state immediately prior to the error-guard's execution and the body of the error-guard is evaluated as the result of the function. This means that, during evaluation of the body, the guard is no longer in effect and so the danger of a hang caused by an infinite "trap loop", is avoided.

Notice that you can provide "cascading" error trapping in the following way:

      0::try_2nd
      0::try_1st
      expr

In this case, if expr generates an error, its immediately preceding: 0:: catches it and evaluates try_1st leaving the remaining error-guard in scope. If try_1st fails, the environment is unwound once again and try_2nd is evaluated, this time with no error-guards in scope.

See also Guards.

Examples:

Open returns a handle for a component file. If the exclusive tie fails, it attempts a share-tie and if this fails, it creates a new file. Finally, if all else fails, a handle of 0 is returned.

open←{                 ⍝ Handle for component file ⍵.
    0::0               ⍝ Fails:: return 0 handle.
    22::⍵ ⎕FCREATE 0   ⍝ FILE NAME:: create new one.
    24 25::⍵ ⎕FSTIE 0  ⍝ FILE TIED:: try share tie.
           ⍵ ⎕FTIE 0   ⍝ Attempt to open file.
}

An error in div causes it to be called recursively with improved arguments.

div←{                  ⍝ Tolerant division:: ⍺÷0 → ⍺.
    ⍺←1                ⍝ default numerator.
    5::↑∇/↓↑⍺ ⍵        ⍝ LENGTH:: stretch to fit.
    11::⍺ ∇ ⍵+⍵=0      ⍝ DOMAIN:: increase divisor.
    ⍺÷⍵                ⍝ attempt division.
}

Notice that some arguments may cause div to recur twice:

       6 4 2 div 3 2
→      6 4 2 div 3 2 0
→      6 4 2 div 3 2 1
→      2 2 2

The final example shows the unwinding of the local environment before the error-guard's body is evaluated. Local name trap is set to describe the domain of its following error-guard. When an error occurs, the environment is unwound to expose trap's statically correct value.

      add←{
          trap←'domain' ⋄ 11::trap
          trap←'length' ⋄  5::trap
          ⍺+⍵
      }
 
      2 add 3         ⍝ Addition succeeds 
5
 
      2 add 'three'   ⍝ DOMAIN ERROR generated.
domain
 
      2 3 add 4 5 6   ⍝ LENGTH ERROR generated.
length

 

Note:

Following the setting of an error-guard, subsequent function calls will disable tail call optimisation:

    {
        22::'Oops!'     ⍝ this error-guard means that ...
        tie←⍵ ⎕ftie 0
        subfn tie       ⍝ ... tail call not optimised
    }

One way to maintain the tail call optimisation in the presence of an error-guard is to isolate it within an inner function:

    {
        tie←{
            22::0        ⍝ error-guard local to inner fn
            ⍵ ⎕ftie 0
        }⍵
        tie=0:'Oops!'
        subfn tie        ⍝ ... so this is a tail call 
    }