Skip to content

J and APL Operators

Vector 13:3, page 112

language design mathematics and statistics

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

This article is a mirror image of "APL Operators and J" [1], the thrust of which was that studying J is a useful exercise in sharpening APL programming skills. This article examines the reverse side of this coin, namely ways in which aspects of APL programming can enhance J programming skills. Until now it has been the case that most users came to J from a knowledge base in APL, but it must be expected that there will be an increasing number of new J users who have no acquaintance with APL.

J allows the definition of verbs, adverbs and conjunctions, and the distinctions between these can be confusing. In J, only verbs are executable, just as in APL operators lead to defined functions which are the ultimate executable objects. In J, both adverbs and conjunctions are means of defining verbs. They are distinguished by the fact that adverbs can have one and only one parameter, whereas the meaning of "conjoin" dictates that conjunctions must have two parameters. These may be any combination of noun and verb, and so conjunctions can be classified as nn, nv, vn and vv. This is best illustrated by an example. Suppose that the following pair of verbs is defined:

   fn=.*:                    NB. fn is the verb "square"
   vb0=.3 :'2+fn y.'         NB. vb0 adds 2 to the square of its argument
   vb0 4
18

The notation vb0=.3 : is akin to APL del in the function header, and allows explicit definition of verbs (that is definitions with explicit reference to arguments), as opposed to tacit programming. An equivalent tacit definition of vb0 is

   vb0=.+&2@fn

An advantage of defining vb0 in this way is that it is possible to parameterise the verb fn by writing an adverb

   ad0=.1 :'+&2@x.'
   fn ad0 4
18

1 : can be thought of as an adverbial equivalent of del, and the rest of the line as resembling the way in which, in the days before APL2 and user-defined operators, APL programmers built up executable lines using character strings, and then prefixed them with "execute". One merit of tacit definition is that it facilitates verb parameterisation through the construction of adverbs.

The 2 in vb0 could also be parameterised. Since there are now two parameters a conjunction must be employed. Define

   con0=.2 : '+&x.@y.'
   2 con0 fn 4
18
   9 con0 %: 4
11

Again the 2 : plays the role of APL del. : is itself an nn conjunction, joining an integer x. (1=adverb, 2=conjunction, 3=verb) and a character string definition y. . In APL terms, : is a realisation of del which makes a distinction between the part of speech which is being defined.

As a second example, here is a verb which uses control structures:

   vb1=.3 :'if. y.<0             NB. returns squares of positive
            do.   r=.%fn y.      NB. values, or reciprocals of
            else. r=.fn y.       NB. squares of negative values
         end.'
   vb1 3
9
   vb1 _3
0.1111111

To parameterise the verb, first obtain a tacit form using gerund:

   vb1=.fn ` (%@fn) @. (0&>)

and define the corresponding adverb

   ad1=.1 :'x. ` (%@x.) @. (0&>)'

   fn ad1 3
9

Recursive functions can also be handled using this technique, for example

   gn=.*                              NB. gn is a verb for parameterisation
   vb2=.1: ` (] gn $:@<:) @. *        NB. evaluate n.(n-1). ... 1
   vb2 4
24

   ad2=.1 :'1: ` (] x. $:@<:) @. *'   NB. define the corresponding adverb
   * ad2 4                            NB. product of first 4 +ve integers
24
   + ad2 4                            NB. 1+sum of first 4 +ve integers
11

Given the last result, it might be desirable to parameterise also the recursion stopping verb, which suggests a conjunction:

   con2=.2 :'y. ` (] x. $:@<:) @. *'  NB. define a corresponding conjunction

   +con2 0: 4
10

J contains many more primitive conjunctions than adverbs, and a table of both can be helpful in seeking examples of the various possibilities.

Adverbs:

/ insert /. oblique/key \ train f. fix
} amend ~ commute/reflex b. boolean

Conjunctions:

nn nv vn vv
& x x x bond/compose
&. &: x under/appose
@ @: x atop/at
. .. .: x dot prod, even/odd
:. :: x obverse, adverse
^: x x power
" x x x constant/rank
: ` x x x x explicit defn/tie
@. x agenda
D. x derivative
;. x cut
]. x x dex
[. x x lev
!. x fit(customise)
`: x evoke gerund
!: x foreign conjn.

The defined examples given so far, although trivial in themselves, are useful in developing an understanding of parts of speech in J. Here, in conclusion, is a non-trivial example of translating an APL2 operator into a J equivalent. The problem in question is to write a numerical integrator of reasonable generality, the APL2 prototype for which is a pair of operators described in [2].

The starting point is Simpson’s rule, for which an APL2 operator is:

[0]  Z←L(P SIMPSON)R;T
[1]   Z←(T×+/(1,((L-1)⍴4 2),1)×P¨ (↑R)+(T←-R.-÷L)×0,⍳L)÷3

This realisation of Simpson’s rule combines a function P, together with data which is the append of R, the range of integration, and L, the number of intervals, which must be an even number. Call this aggregate an iplan (integration plan), so that the requirement in J is for something to link the integrand (verb) and an iplan. Since we are joining a verb and a vector, the first idea might be to write a vn conjunction. However the iplan is the entire data, and consideration of the vn conjunctions such as power and rank shows that this type of conjunction is appropriate only when the noun conjunct is part of the data. Put in another way, a conjunction is not in itself executable, and so some of the data has to be "held back" for the point at which a verb ultimately achieves control. It is therefore an adverb which must be sought to describe how to execute the integrand, namely at the points of the grid which the Simpson technique calls for. A grid idiom (gridiom!) derived from Chris Burke’s admirable J Phrase Book [3] distributed with J Release 3 is the following:

   grid=.+`(*i.)/            NB. y.=left-value, grid-unit, # of points
   grid _5 2 5
_5 _3 _1 1 3

To turn an iplan into a grid it is necessary first to compute the Simpson sub-interval:

   subint=.%@(%`-/@|.)       NB. sub-interval for iplan=range, # of ints
   subint 3 4 4
0.25

and to be able to identify the number of intervals, which is one less than the number of grid points:

   last=.{.@|.               NB. last item in vector as scalar

This is all that is necessary to convert an iplan into a grid, using an adverb to describe how (that is, for what values) the integrand is to be applied:

   itogly=.@(grid@({.`subint`(>:@last)/.))  NB. by turning iplan into grid

   *: itogly 3 4 4
9 10.5625 12.25 14.0625 16

Next the Simpson weights have to be calculated. There are several ingenious ways of doing this, but the following algorithm is as comprehensible as any:

   swts=.3 :'1,((_1+_1{.y.)$4 2),1'  NB. Simpson weights for iplan

   swts 3 4 4
1 4 2 4 1

Next these have to be multiplied by one-third of the Simpson interval:

   swt=.%&3@(subint * swts)          NB. Adjusts swts for 1/3.h
   swt 3 4 4
0.0833333 0.333333 0.166667 0.333333 0.0833333

and finally here is the Simpson adverb which expresses the fact that the Simpson estimate is the inner product of swt and the function values at the appropriate grid points:

   simp=.1 : 'swt +/ .*x.itogly'    NB. Simpson adverb, e.g. fn simp 0 1 4

   *: simp 3 4 4                    NB. Simpson's rule is exact for this fn.
12.3333

The APL2 operator ADAPT achieves adaptive integration, which is powerful generalisation of SIMPSON.

[0]  Z←EPS(P ADAPT)R
[1]   Z←4(P SIMPSON)R
[2]   →(EPS>|Z-2(P SIMPSON)R)/0
[3]   Z←+/(0.5×EPS)(P ADAPT)¨0 1⌽¨R,¨0.5×+/R

The idea is that the values obtained by applying the Simpson rule for two and four sub-intervals are compared. If these are within a tolerance, which now replaces the number of intervals as the final item of the iplan, then the four-sub-interval value is accepted. If this is not the case, half the tolerance is allocated to each half of the range, and the process is repeated for each half-range. The process of range sub-division continues until all the sub-ranges satisfy the above condition. A first step, then, is to write an adverb simd, which modifies an integrand by describing how to compute the difference between its two- and four-interval Simpson estimates:

   simd=.1 :'x.sim2-x.sim4'     NB. Difference between 2 and 4 subints.
    sim2=.1 :'x.simp@,&2'       NB. Simpson rule with 2 sub-intervals
    sim4=.1 :'x.simp@,&4'       NB. Simpson rule with 4 sub-intervals

Next write a verb which generates the new iplans when tolerance is not met:

   nip=.(hr@range),each -:@last    NB. Two new iplans halving range & tolerance
     range=.2&{.                   NB. Range of integration = 2-item vector
     hr=.(i.@#)|.each(,each-:@+/)  NB. Makes two half-ranges from one
     each=.&.>                     NB. Each adverb = "under open"

and finally a verb which, for a given function f, realises the algorithm described above in words.

   simf=.3 :'if.(last y.)<|f simd range y.
             do.   r=.+/>simf each nip y.
             else.r=.f sim4 range y.
          end.'

   f=.3 :'(^y.)*1+1 o.y.'          NB. function f(x)=exp(x).(1 + sin x)

   simf 0 1 0.000001               NB. integral of from 0 to 1 corr. to 5 d.p.
2.62761

As an aside, the steps taken by the adaptive algorithm can be traced by omitting the +/ from simf, and rerunning. simf is readily converted into a tacit recursive form:

   simt=.stop`(+/@(>@($: each@nip)))@.test  NB. Recursion using gerund
    stop=.f sim4@range                      NB. Stopping verb
    test=.last<|@(f simd@range)             NB. Stopping test

leading finally, by the technique described earlier, to an adverb in which f is parameterised.

   adapt=.1 :'x.sim4@range`(+/@(>@($: each@nip)))@.(last<(|@(x.simd@range)))'

   ":!.9 f adapt 0 1 0.000001     NB. Make J display 9 sig figs.
2.62761245

In summary, here are the full definitions of simp and adapt:

simp=.1 : 'swt +/ .*x.itogly'  NB. Simpson adverb, e.g. fn simp 0 1 4
 swt=.%&3@(subint * swts)      NB. Adjusts swts for 1/3.h
  subint=.%@(%`-/@|.)          NB. sub-interval for iplan=range, # of ints
  swts=.3 :'1,((_1+_1{.y.)$4 2),1'  NB. Simpson weights for iplan y.
 itogly=.@(grid@({.`subint`(>:@last)/.))  NB. by turning iplan into grid
  grid=.+`(*i.)/               NB. left-value, grid-unit, no of points
  last=.{.@|.                  NB. last item in vector as scalar
adapt=.1 :'x.sim4@range`(+/@(>@($: each@nip)))@.(last<(|@(x.simd@range)))'
 simd=.1 :'x.sim2-x.sim4'       NB. Difference between 2 and 4 subints.
  sim2=.1 :'x.simp@,&2'         NB. Simpson rule with 2 sub-intervals
  sim4=.1 :'x.simp@,&4'         NB. Simpson rule with 4 sub-intervals
  range=.2&{.                   NB. Range of integration = 2-item vector
 nip=.(hr@range),each -:@last   NB. Two new iplans halving range & tolerance
  hr=.(i.@#)|.each(,each-:@+/)  NB. Makes two half-ranges from one
  each=.&.>                     NB. Each adverb = "under open"

References

[1] APL Operators and J, Norman Thomson, Vector Vol.13, No. 1 pp.143-148

[2] Technical Note on Confidence Limits, Norman Thomson, Vector Vol.12 No.4, pp.103-104

[3] J Phrase Book, Chris Burke, distributed with J rel. 3, 1996