>>  <<  Usr  Pri  JfC  LJ  Phr  Dic  Rel  Voc  !:  Help  Learning J

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
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
If an assertion is evaluated as true (or "all true") there is no other effect, and the computation proceeds.
   carpet 2 3 4
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

   carpet 2 3 4
   carpet 'hello'
|assertion failure: carpet
|   (3!:0 y)e.4 8

29.1.1 Assertions and the Tacit Style

Assertions are good for correctness. The tacit style is good for crispness and clarity.

The two are not readily combined, however. Evidently the natural place for an assertion is as a line in an explicit definition. By contrast, a tacit definition offers no place for an assertion.

What would it take to add assertions to a set of purely tacit definitions? Just to be able to make assertions about the arguments of functions would be a lot better than nothing. Here is a possibility.

Suppose we have an example of a purely tacit definition,

   sq =: *:
and we wish to assert that any argument to sq must be a number, that is, it must satisfy the predicate:
   is_number =: 4 8 16 128 e. ~ (3 !: 0)
Now our aim is to redefine sq, while making use of the previous definition of sq. Convenient for this purpose is a conjunction ASSERTING, which is defined below.

We can write

   sq =: sq ASSERTING is_number
and we see:
3 : 0
assert. (is_number) y
(*:) y
   sq 3
   sq 'abc'
|assertion failure: sq
|   (is_number)y
The definition of ASSERTING is:
2 : 0
    U =. 5!:5  < 'u'
    if. (< U) e. nl 3 do. U =. 5!:5 < U end.
    V =. 5!:5  < 'v'
    z =: 'assert. (', V , ') y', LF
    z =. z , '(', U,  ') y'  
    3 : z
The ASSERTING conjunction is written in this string-building style so that its result can be easily inspected. We can see that the new sq combines the predicate is_number with the value (not the name!) of the old sq. Finally, note that ASSERTING as here defined is good only for monadic verbs.

29.1.2 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
   (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

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. Here is an example of a test-script.
   (0 : 0) (1!:2) <'test.ijs'   NB. create test-script

NB. test 1 
carpet 10  0 30       

NB. test 2
carpet 10 20 30       
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!:11 <'test.ijs'            NB. execute test-script 
   NB. test 1 
   carpet 10  0 30       
|assertion failure: carpet
|   *./y>0
   NB. test 2
   carpet 10 20 30       

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'
A word not in the list will produce an error.

ef 'two' ef 'seven'
deux error

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 'two' EF 'seven'
deux 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

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.

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

|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.

Table of Contents

The examples in this chapter were executed using J version 701. This chapter last updated 14 Oct 2012
Copyright © Roger Stokes 2012. This material may be freely reproduced, provided that this copyright notice is also reproduced.

>>  <<  Usr  Pri  JfC  LJ  Phr  Dic  Rel  Voc  !:  Help  Learning J