Skip to content

A Direct Definition Handler

Vector 6:1, page 110

programming techniques language design

Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 110

Abstract

This article describes an unusual application for error handling in the APL*PLUS II system for 386 based machines, namely a facility for interpreting direct definition statements in immediate execution mode. The direct definition facility is described first, followed by an exposition of its implementation using the available error handling techniques.

Introducing Direct Definition

Direct definition is a notation for associating an APL statement with a name, for example:

      TWICE: 2×⍵

A function TWICE is thereby defined, which multiplies its argument by two. The colon separates the function name from its definition, and the ⍵ (omega) represents the argument. The function can then be used:

      TWICE 1 2 3
2 4 6

If an ⍺ (alpha) appears in the definition the function can be called dyadically, the ⍺ representing the function left argument. A niladic function is defined when neither ⍺ nor ⍵ appear. For example:

      PLUS: ⍺+⍵
      PI: ○1
      1 PLUS TWICE PI
7.283185307

A three-segment form of definition is used to allow one of two alternative statements to be conditionally executed:

      FACT: ⍵ : ⍵>1 : ⍵×FACT ⍵-1
      FACT 10
3628800

The middle segment, the condition segment, is executed first. It must evaluate to a 0 or 1. If its value is 0 then the left segment is executed. If the value of the condition segment is 1 then the statement in the right segment is executed instead. The three-segment form is often used with recursion, or to determine if a left argument has been supplied.

A definition can be displayed by typing its name followed by a colon, for example:

      PLUS:
PLUS: ⍺+⍵

Essentially, that’s all there is to direct definition. A future issue of Vector may well contain an article describing the APL*PLUS II Direct Definition Handler facility, its uses and benefits in detail. The remainder of this article is concerned with how the APL*PLUS II error trapping tools are used to make the facility work.

Why is Direct Definition so Scarce?

Although various forms of direct definition have been used as a notation for a long time, its use in programming commercial APL applications has been very limited. There are two main reasons for this.

Firstly, direct definition has only traditionally been made available through a set of cover functions which perform the appropriate operations, for example:

      DD 'PLUS: ⍺+⍵'
PLUS

      DD 'PLUS:'
PLUS: ⍺+⍵

The use of direct definition cover functions makes the tool unwieldy; you have to type the cover function name followed by the definition inside quotes. If your definition contains quotes then you have to double them up.

Secondly the commercial APL vendors have not included direct definition as a primitive facility in their interpreters. The public domain interpreter I-APL, which was written by Paul Chapman, is the only APL system which does do this, but I-APL is not appropriate for commercial work.

What is needed is a fast, commercial APL which either provides direct definition as a primitive facility, or allows for its provision in a natural way.

Direct Definition in APL*PLUS II

The APL*PLUS II system for 386 machines satisfies the latter condition, and for an interesting reason, which we now explore.

In APL defined functions, the form

    label:line

is understood to be a labelled line, and we certainly do not want to change that. What is the meaning of this form in immediate execution mode?

Most APL systems actually tolerate this form in immediate execution mode, without reporting an error of any kind. What happens is that the line is executed and the label ignored. The draft APL standard (hopefully soon to be published by the International Standards Organisation) reports an error for this construct, so the observed behaviour (allowing a label) is a perfectly legal consistent extension, but of course a pointless one.

The APL*PLUS II system however, is close to the formal model in the standard in that it signals an error when a labelled line is typed in immediate execution mode, as follows:

      LAB: 1+2
SYNTAX ERROR
      LAB: 1+2
         ^

Here comes the good bit - this error can be trapped, analysed and then reinterpreted as a potential direct definition statement. All that is needed is a suitable global ⎕ELX and an appropriate APL function to do the analysis. This function is called DDHANDLE, and it needs to be in the workspace. Here is what the ⎕ELX looks like after the Direct Definition Handler has been enabled:

      DDHANDLE 'ON'
The Direct Definition Handler is now on.

      ⎕ELX
⍎(~DDHANDLE ⎕DM)/'⎕DM'

Let’s consider what happens with this error latent expression when an error occurs. When the error is signalled by the system, the diagnostic message is passed into the analysis function DDHANDLE. Then one of two things can happen, dependent upon whether the diagnostic message contains a potential direct definition statement or not. The diagnostic message is considered a potential direct definition statement if it contains a caret that points to a colon, as above.

Let us first suppose that this condition is not met; in this case DDHANDLE will return a 0, ⎕DM is executed, and the diagnostic message is reported to the user in the normal way:

      1 2 + 1 2 3
LENGTH ERROR
      1 2 + 1 2 3
      ^

If the diagnostic message does correspond to an attempted direct definition statement then there are again two possibilities; if the statement is not a valid direct definition request, an appropriate error message (DDEFN ERROR) is signalled in the calling environment by DDHANDLE, using the ⎕ERROR system function. For example:

      A B: 1+2
DDEFN ERROR
      A B: 1+2
      ^

If the diagnostic message contains a valid direct definition statement however, then DDHANDLE returns 1, an empty vector is executed, and no error report is made. In this case DDHANDLE also quietly defines an APL function whose name and definition are determined by the definition request entered, e.g.:

      DAB: ⍵~' ' ⍝ Delete All Blanks

      )FNS
DAB DDHANDLE FACT PI PLUS TWICE

In this way error-signalling APL statements which are not considered to be potential direct definition requests are transparent to the Direct Definition Handler, and are reported by APL in the way that the user expects.

In fact, thanks to a suggestion by James A Brown, the Direct Definition Handler does not even prevent you from having your own specialised error handling routine active at the same time. Suppose we want to embellish the system’s usual error reporting with a bell sound and a more friendly message. We might use a ⎕ELX like this:

      ⎕ELX←⍞
⎕SOUND 1 2⍴200 ⋄ 'YOU MADE A ',⎕DM

It works like this:

      1 2 + 1 2 3
YOU MADE A LENGTH ERROR
      1 2 + 1 2 3
      ^

You probably didn’t hear the bell ring, but you can see the message.

But now we also want to use direct definition. Lets enable the handler and take a look at the ⎕ELX:

      DDHANDLE 'ON'
The Direct Definition Handler is now on.

      ⎕ELX
⍎(~DDHANDLE ⎕DM)/'⎕SOUND 1 2⍴200 ⋄ ''YOU MADE A '',⎕DM'

Your own ⎕ELX got imbedded in the new one, but it will only be executed if a non direct definition error occurs, as shown here:

      DAB:
DAB: ⍵~' ' ⍝ Delete All Blanks

      A B: 1+2
DDEFN ERROR
      A B: 1+2
      ^

      1 2 + 1 2 3
YOU MADE A LENGTH ERROR
      1 2 + 1 2 3
      ^

The handler can be turned off to restore the prior value of ⎕ELX as follows:

      DDHANDLE 'OFF'
The Direct Definition Handler is now off.

      ⎕ELX
⎕SOUND 1 2⍴200 ⋄ 'YOU MADE A ',⎕DM

Using Directly Defined Functions Inside Other Programs

Perhaps you want to use direct definition inside APL programs. Clearly it’s not possible to type a definition directly because your entry will be interpreted as a labelled line, as explained at the start of this article. To do this we must use a support function for defining functions on the fly.

It’s very useful to be able to define a function directly when you want to apply it to a nested array with the ‘each’ operator. As a trivial example, consider the problem of finding the rank of each item in a nested array. We could write:

      RE←¨⍴¨⍴¨ARRAY

but this requires three eaches (loops) and is not in general easy to read.

The alternative is to define a function RANK and to apply it to each item of the argument, as follows:

      DDdef 'RANK: ⍴⍴⍵' ⋄ RE←RANK¨ARRAY

The function DDdef establishes the function defined in its argument; you can then use it immediately. Uses of this technique will be covered in a future article; for now we will stick to error handling. Can you see how DDdef might work?

Here is its definition:

      DDdef: ⍎⍵ ⍙ ⎕ELX←⎕ELX,' ⋄ →0' ⍙ DDHANDLE 'ON' ⍝/NR

      ⍙:⍺

The function ⍙ merely returns its left argument and so acts as a right-to-left operating statement separator.

Firstly the handler is enabled by running DDHANDLE 'ON'. This establishes the appropriate ⎕ELX, which is then modified so that DDdef is not left on the SI stack when the execute completes. Finally , the direct definition statement itself, is executed. This of course signals an error which is picked up by the handler, and the function is defined.

Don’t worry, the handler automatically localises any variable names that appear directly to the left of the assignment arrow, so setting ⎕ELX within DDdef does not modify its global value.

The ⍝/NR tells the handler not to extract an explicit result from the execute primitive, which in this case will not produce one.

Summary

The APL*PLUS II Direct Definition Handler has proved to be a powerful and versatile tool. The two factors that made it possible are the APL*PLUS error trapping facilities and the interpreter’s behaviour for the label form in immediate execution mode. The three factors that make it a joy to use are the absence of direct definition cover-functions, the fact that only a single support function is required in the workspace, and the speed of the 386 hardware.