Ch 29: Error Handling

| # Chapter 29: Error Handling The plan for this chapter is to look at some of the J facilities for finding and dealing with programming errors. It is beyond the scope of this chapter to consider debugging strategies, but (in my view) the use of assertions is much to be recommended. We look at: Assertions Continuing after failure Suspended execution Programmed error-handling ## 29.1 Assertions A program can be made self-checking to some degree. Here is an example of a verb which computes the price of an area of carpet, given a list of three numbers: price per unit area, length and width.
       carpet =: 3 : 0
    */ y.
    )

       carpet 2 3 4
    24
Assume for the sake of example that the computation `*/y.` is large and problematic, and we want some assurance that the result is correct. We can at least check that the result is reasonable; we expect the price of a carpet to lie between, say, $10 and $10,000. We can redefine `carpet`, asserting that the result `p` must be between 10 and 10000:
       carpet =: 3 : 0
    p =. */y.
    assert. p >: 10
    assert. p <: 10000
    p
    )
If an assertion is evaluated as true (or "all true") there is no other effect, and the computation proceeds.
       carpet 2 3 4
    24
If an assertion is evaluated as false, the computation is terminated and an indication given:
       carpet 0 3 4
    |assertion failure: carpet
    |   p>:10
Assertions can only be made inside explicit definitions, because `assert.` is a "control word", that is, an element of syntax, not a function. It always a matter for judgement as to where an assertion can usefully be placed, and what can usefully be asserted. Assertions are best kept as simple as possible, since it is highly undesirable to make an error in an assertion itself. It is often useful to make assertions which check the correctness of arguments of functions. For example, we could assert that, for `carpet` the argument `y.` must be a list of 3 strictly positive numbers. The order of assertions may be important. For example, we should check that we have numbers before checking the values of those numbers. The type of a noun is given by `3!:0`; here we want integers (type=4) or reals (type=8).
       carpet =: 3 : 0
    assert. (3!:0 y.) e. 4 8   NB. numeric
    assert. 1 = # $ y.         NB. a list (rank = 1)
    assert. 3 = # y.           NB. of 3  items
    assert. *. / y. > 0        NB. all positive
    p =. */y.
    assert. p >: 10
    assert. p <: 10000
    p
    )

       carpet 2 3 4
    24

       carpet 'hello'
    |assertion failure: carpet
    |   (3!:0 y.)e.4 8
### 29.1.1 Enabling and Disabling Assertions When we are confident of correctness, we can consider removing assertions from a program, particularly if performance is an issue. Another possibility is to leave the assertions in place, but to disable them. In this case, asserted expressions are not evaluated, and assertions always succeed. There is a built-in function `9!:35` to enable or disable assertions. For example:
       (9!:35) 0      NB. disable assertions
       carpet 0 3 4   NB. an error
    0

       (9!:35) 1      NB. enable assertions
       carpet 0 3 4   NB. an error
    |assertion failure: carpet
    |   *./y.>0
The built-in function `9!:34` tests whether assertions are enabled. Currently they are:
       9!:34 ''   NB. check that assertions are enabled
    1
## 29.2 Continuing after Failure There are several ways to continue after a failure. ### 29.2.1 Nonstop Script In testing a program, it may be useful to write a script for a series of tests. A test may give the wrong result, or it may fail altogether, that is, it may be terminated by the system. We can force the script to continue even though a test fails, by executing the script with the built-in verb `0!:10` or `0!:11`
       (0 : 0) (1!:2) <'test.ijs'   NB. create test-script
    carpet 10  0 30       NB.  test 1
    carpet 10 20 30       NB.  test 2
    )

       0!:11 <'test.ijs'
       carpet 10  0 30       NB.  test 1
    |assertion failure: carpet
    |   *./y.>0
       carpet 10 20 30       NB.  test 2
    6000
### 29.2.2 Try and Catch Control Structure Here is an example of a verb which translates English words to French using word-lists.
       English =: 'one'; 'two';  'three'
       French  =: 'un';  'deux'; 'trois'

       ef =:  3 : '> (English i. < y.) { French'

       ef 'two'
    deux
A word not in the list will produce an error.
       ef 'seven'
    |index error: ef
    |   >(English i.< y.)    {French
This error can be handled with the `try. catch. end.` control structure. ( Chapter 12 introduces control structures)
       EF =: 3 : 'try. ef y. catch. ''don''''t know'' end.'

       EF 'seven'
    don't know
The scheme is that
             try. B1 catch. B2 end.
means: execute block B1. If and only if B1 fails, execute block B2. ### 29.2.3 Adverse Conjunction A tacit version of the last example can be written with the "Adverse" conjunction `::` (colon colon).
       TEF =: ef :: ('don''t know' " _)
`TEF 'two'` | `TEF 'seven'`
    deux
    don't know
Notice that the left and right arguments of `::` are both verbs. The scheme is:
           (f :: g) y
means: evaluate `f y`. If and only if `f y` fails, evaluate `g y` ## 29.3 Suspended Execution Suppose we have, as an example of program to be debugged, a verb `main` which uses a supporting verb `plus`
       main  =: 3 : 0
    k =. 'hello'
    z =. y. plus k
    'result is'; z
    )

       plus =: +
Clearly there is an error in `main`: the string `k` is inconsistent with the numeric argument expected by `plus`. If we type, for example, `main 1` at the keyboard, then when the error is detected the program terminates, an error-report is displayed and the user is prompted for input from the keyboard.
       main 1
    |domain error: plus
    |   z=.y.     plus k
To gather more information about the cause of the error, we can arrange that the program can be suspended rather than terminated when control returns to the keyboard. To enable suspension we use the command `(13!:0) 1` before running `main` again.
       (13!:0) 1
Now when `main` is re-run, we see a slightly different error message
       main 1
    |domain error: plus
    |plus[:0]
At this point execution is suspended. In the suspended state, expressions can be typed in and evaluated. Notice that the prompt is 6 spaces (rather than the usual 3) to identify the suspended state.
          1+1
    2
We can view the current state of the computation, by entering at the keyboard this expression, to show (selected columns of) what is called the "execution stack".
          0 2 6 7 8 { " 1 (13!:13 '')
    +----+-+---------+----------+-+
    |plus|0|+-+-----+|          |*|
    |    | ||1|hello||          | |
    |    | |+-+-----+|          | |
    +----+-+---------+----------+-+
    |main|1|+-+      |+--+-----+| |
    |    | ||1|      ||k |hello|| |
    |    | |+-+      |+--+-----+| |
    |    | |         ||y.|1    || |
    |    | |         |+--+-----+| |
    +----+-+---------+----------+-+
The stack is a table, with a row for each function currently executing. We see that `plus` is the function in which the error was detected, and `plus` is called from `main`. The stack has 9 columns, of which we selected only 5 for display (columns 0 2 6 7 8). The columns are:
0 | Name of suspended function. Only named functions appear on the stack.

1 | (not shown above) error-number or 0 if not in error

2 | Line-number. `plus` is suspended at line 0 and `main` is at line 1

3 | (not shown above) Name-class: 1 2 or 3 denoting adverb, conjunction or verb

4 | (not shown above) Linear representation of suspended function

5 | (not shown above) name of script from which definitions were loaded

6 | Values of arguments. `plus` was called with arguments `1` and `'hello'`

7 | Names and values of local variables. `plus` being a tacit verb has no local variables, while `main` has `k` and also `y.`, since arguments of explicit functions are regarded as local variables
.
8 | An asterisk, or a blank. `plus` is asterisked to show it is the function in which suspension was caused. Normally this the top function on the stack, (but not necessarily, as we will see below).
While in the suspended state we can inspect any global variables, by entering the names in the usual way. In this simple example there are none. Finally, we can terminate the suspended execution, and escape from the suspended state, by entering the expression:
       (13!:0) 1
## 29.4 Programmed Error Handling By default, when suspension is enabled, and an error is encountered, the program suspends and awaits input from the keyboard. We can arrange that instead of taking input from the keyboard, when an error is encountered, our own error-handling routine is automatically entered. Suppose we decide to handle errors by doing the following: display the error message generated by the system display (selected columns of) the stack cut short the execution of the the suspended function, and cause it to return the value `'error'` instead of whatever it was intended to return. resume executing the program. (This may or may not result in a cascade of further errors.) Here is a verb to perform this sequence of actions:
       handler =: 3 : 0
    (1!:2&2) 13!:12 ''                  NB. display error message
    (1!:2&2) 0 2 6 7 8 {" 1 (13!:13 '') NB. display stack
    13!:6 'error'                       NB. resume returning 'error'
    )
The next step is to declare this verb as the error-handler. To do this we set an appropriate value for what is called the "latent expression". The latent expression is represented by a string which, if non-null, is executed automatically whenever the system is about to enter the suspended state. The latent expression can be queried and set with `13!:14` and `13!:15`. What is the current value of the latent expression?
       13!:14 ''
A null string. We set the latent expression to be a string, representing an expression meaning "execute the verb `handler`".
       13!:15 'handler 0'
Now we make sure suspension is enabled:
       (13!:0) 1 NB. enable suspension
and try a debugging run on `main`
       main 1
    |domain error: plus
    |plus[:0]

    +-------+-+---------+----------+-+
    |handler|1|+-+      |+--+-+    | |
    |       | ||0|      ||y.|0|    | |
    |       | |+-+      |+--+-+    | |
    +-------+-+---------+----------+-+
    |plus   |0|+-+-----+|          |*|
    |       | ||1|hello||          | |
    |       | |+-+-----+|          | |
    +-------+-+---------+----------+-+
    |main   |1|+-+      |+--+-----+| |
    |       | ||1|      ||k |hello|| |
    |       | |+-+      |+--+-----+| |
    |       | |         ||y.|1    || |
    |       | |         |+--+-----+| |
    +-------+-+---------+----------+-+
    +---------+-----+
    |result is|error|
    +---------+-----+
We see that the topmost stack-frame is for `handler`, because we are in `handler` when the request to view the stack is issued. The suspended function is `plus`. The display `result is error` demonstrates that `plus` returned the value (`'error'`) supplied by `handler`. This is the end of Chapter 29.

NEXT
Table of Contents
Index

Copyright © Roger Stokes 2003. This material may be freely reproduced, provided that this copyright notice is also reproduced.

last updated 1 Oct 2003