Skip to content

APL Operators and J

Vector 13:1, page 143

language design programming techniques

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

J merits the attention of all serious APL programmers, because of the way in which it has brought the nature of APL operators into sharper focus.

Roughly speaking "function" in APL corresponds to "verb" in J. However J splits the concept of an APL operator in two, dividing it between that of "adverb", (strictly monadic), and "conjunction" (strictly dyadic). Conjunctions in J subdivide further into the types noun-noun, noun-verb and verb-verb, so that in total there are four identifiably different types of APL operators. The insight thus gained can have a profound effect on APL programming. For conciseness I shall call the resulting style "operator programming", and I claim that it can result in a great increase in generality of code produced.

Before considering the four classifications separately, I will illustrate what I mean by "operator programming", using what must be one of the first functions which most APL programmers write, namely mean:

[0] Z←mean R
[1]  Z←(+/R)÷⍴R

      mean 1 4 5 6
4

Taking an operator view, and with a fork in J in mind, rewrite this by placing the ÷/ at the left hand end of the expression, where it comes as a climax following two transformations on R, the first ⍴ and the second +/. Connecting these two functions with an operator AND which applies both its function operands to the data:

[0] Z←L(P AND Q)R
[1]  →(0=⎕NC 'L')/L1
[2]  →0 Z←(L P R)(L Q R)
[3] L1:Z←(P R)(Q R)

leads to a revised definition of mean:

[0] Z←mean1 R
[1]  Z←÷/+/AND⍴R

      mean1 1 4 5 6
 4

(The astute reader will have noticed the blank on the left which means that the result of mean1 has depth two; this is a programming technicality which is readily dealt with in a production context.)

It is well known that the arithmetic mean is not the only form of average — indeed this is often the lead-in to the APL novice’s second exercise which includes geometric and harmonic mean. Put in another way, there is a requirement to average values of other functions of the data, which in turn is an invitation to generalise mean1 to an operator:

[0] Z←(P MEAN1)R
[1]  Z←÷/+/AND⍴P R

      +MEAN1 1 4 5 6
4

The harmonic mean is

      ÷÷MEAN1 1 4 5 6
2.4742

and the geometric mean is

      *⍟MEAN1 1 4 5 6
3.3098

The inverse function which is the leftmost token in each of the last two expressions is, to say the least, tiresome. Remember that a second operand is still available, so re-define MEAN1 yet again:

[0] Z←(P MEAN Q)R
[1]  Z←P÷/+/AND⍴Q R

      +MEAN+ 1 4 5 6
4

leading to the following expressions for harmonic and geometric means:

      ÷MEAN÷ 1 4 5 6
      *MEAN⍟ 1 4 5 6

To program the absolute deviation from the mean, start with a function:

[0] Z←adev R
[1]  Z←∊|R-+MEAN+R

which, used in conjunction with MEAN, gives the absolute deviation as:

      +MEAN adev 1 4 5 6
1.5

Now stop and reflect — absolute deviation is only one form of deviation, another form is the squared deviation, whose average is the variance, so again "think operator":

[0] Z←(P DEV)R
[1]  Z←∊P R-+MEAN+R

[0] Z←sq R
[1]  Z←R*2

      +MEAN |DEV 1 4 5 6
1.5
      +MEAN sq DEV 1 4 5 6
3.5

Next the function sq is itself just a particular case of something more general, so applying the same psychology define:

[0] Z←(P EXP)R
[1]  Z←R*P

      +MEAN(2 EXP)DEV 1 4 5 6
3.5
      +MEAN(3 EXP)DEV 1 4 5 6
¯4.5

the latter expression, for example, giving the third moment about the mean.

To sum up the essence of "operator programming" in a phrase — why write one function when you can write several for the same effort!

The next section describes instances of each of the four types of operator identified at the start.

1. Operators used adverbially

Primitive instances are reduction and outer product, (but NOT inner product which is a case of verb-verb). The discussion above includes a simple adverbial operator EXP. You may be tempted to think that there is little apparent difference between 2 EXP 3 and 2*3. On the contrary, there is a very great deal. For one thing (2*)3 is a syntax error, whereas (2 EXP)3 is not only equivalent to 2 EXP 3, but also demonstrates an underlying device which, in computer science, is called "currying" after the American logician H.B.Curry. Roughly speaking, it involves replacing an operation with two arguments with an operation having only one, by binding one of the operands to the operation. Thus 2 EXP is a composite function which can be applied to each of 1 2 3 to obtain 2 4 8.

Another common example of currying is

[0] Z←(P LOG)R
[1]  Z←P⍟R

      10 LOG 2
0.30103

DEV above is a further example of an adverbial operator. It modifies its function operand by adverbially intruding "having subtract the mean" between the function operand and its argument.

2. Operations of type noun-verb

A simple example of this type of operator usage is RPT1 which is designed to repeat the execution of a function a given number of times:

[0] Z←L(P RPT1)R;I
[1]  I←0 ⋄ Z←L
[2] L1:→(R≤I←I+1)/0
[3]  →L1 Z←P Z

      2(2 EXP)RPT1 5
65536

that is the fifth term of the series 2,4 .. where each term is the square of the preceding term.

A variation on this theme is the operator RPT2 which executes a function repeatedly until two successive iterates are sufficiently close together as defined by a tolerance operand Q:

[0] Z←L(P RPT2 Q)R
[1]  Z←L P R
[2] L1:→(Q>|Z-R)/0
[3]  →L1 Z←L P R←Z

For example, a well known algorithm for finding the square root of a real number consists of iterating the following algorithmic step:

[0] Z←L sqrtstep R
[1]  Z←0.5×R+L÷R

until an adequate approximation is obtained:

      9 sqrtstep RPT2 0.001 1
3

Another example where an operand is a limit value is the operator RPT defined in [1].

3. Operations of type verb-verb

The discussion above contains two examples of this class, namely AND and MEAN. The principle is that the two functions concerned form a composition in a manner which is detailed within the operator definition. Another example, mirroring the adverb of the same name in J, is :

[0] Z←L(P WITH Q)R
[1]  →(0=⎕NC 'L')/L1
[2]  →0 Z←(Q L)P(Q R)
[3] L1:Z←(P R)(Q R)

      10* 2 +WITH(10 LOG)3
6

which models the process of multiplication by using logarithms.

4. Operations of type noun-noun

These involve a different kind of operator usage. By using currying, they allow an extension of the number of function arguments to three or four. A simple example is the formula for compound interest, which is, in essence, A(1+R)N, and involves three arguments. Currying the (1+R) we have the operator

[0] Z←L(P CI)R
[1]  Z←L×P*R

      100 (1.1 CI) 2 3
121 133.1

The formula for the area of a triangle, given the lengths of its sides is √s(s−a)(s−b)(s−c) — again this has three arguments, so curry on one of them:

[0] Z←L(P TRIAREA)R
[1] Z←(×/(0.5×+/L,P,R)-0,L,P,R)*0.5

  3(4 TRIAREA)5 6

Is operator programming a "better" from of programming? I believe so. The little suite of operations which were developed in the first part of this article included four operators:

AND, MEAN, DEV, EXP

and no functions.

By seeking opportunities to use operators, the APL programmer is forced to ask questions about what are (a) essential, and (b) generalisable about operations. For example, the content of the operators described above shows that:

the essence of MEAN is ÷ +/ and ⍴
the essence of DEV is ∊ and MEAN
the essence of EXP is *

and each of these operators is generalisable in ways which would not be apparent if functions alone had been employed.

The above discussion has tried to show how operators develop naturally when an APL programmer is receptive to opportunities for generalisation. Relatively simple examples were used in order that programming complexity should not interfere with the main thrust of the argument. A reader seeking an illustration of operator programming applied in greater earnest is referred to [1].

Reference

[1] Technical Note on Confidence Limits, N. Thomson, The Random Vector, Volume 12, No.3, pp. 98-106, April 1996