J and APL Operators
Vector 13:3, page 112
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
An advantage of defining vb0 in this way is that it is possible to parameterise the verb fn by writing an adverb
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
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:
and define the corresponding adverb
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:
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:
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:
To turn an iplan into a grid it is necessary first to compute the Simpson sub-interval:
and to be able to identify the number of intervals, which is one less than the number of grid points:
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:
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