Skip to content

J-ottings 8: Transcribing from APL to J

Vector 12:3, page 27

language design education

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

First some statistics. The end of a year is a good time to assess the impact of J in the world, and by a happy coincidence Jan 92 was the first number of "Vector" which carried technical material on J. To assess the strength of J relative to APL, I counted the proportion of technical pages in "Vector" which were devoted to the two languages. I have used the term "technical page" to exclude, for example, advertisements, editorials, product guides, and the social kinds of conference report. While there is inevitably a degree of vagueness in what constitutes a technical page, this does not obscure the broad trends, which the figures below show clearly.

         %age of tech.     overall      Total no    %age of APL9x
         pages about J     %age of    of technical    conference
year    Jan May Jul Oct       J          pages       papers on J

1992     42  35  21  24      31           365              3
1993     23  27   0  21      24           296             16
1994     10  35  26  27      23           438             10
1995     21  79  17  42      41           434             28

These figures would suggest that in terms of articulation, J is now about half as strong as APL. A calculation in terms of revenue would probably tell a different story. Some of the early J material was at a rather advanced level, which may help to account for the 0 in Jul 93. It was this which in turn provoked the first of these columns in Oct 93, these being in some sense a plea that "Vector" should be a vehicle for more graduated introductory material on J. Articles such as that by Chris Burke in Vector 12.2 are a sign that "Vector" is indeed becoming a such a medium.

The starting point for the following thoughts is Walter Spunde’s article in Vector 12.1 entitled "J – Where Have All the Variables Gone?". As always my goal is to examine J by the morsel rather than the mouthful!

Even the most starry-eyed J enthusiast will probably admit, if pressed, that it is easier to realise a programming idea quickly in APL than in J, and J learners must certainly find themselves in the situation of "I know how to do that in APL, but how do I convert it to J?". Walter in his article effectively does a transcription of the APL expression

(↑R) + ((¯1↑R) - ↑R) ÷ L) × ⍳L+1

into J, and the solution he arrives at would be essentially the same as that arrived at by any other literal transcriber, namely

{.@] + (({: @] - {.@] )%[)*(i.@([+1:))

I will return to this example later with a view to examining alternatives, but first I want to pursue an analogy between transcribing APL to J and making transcriptions in music. The latter occurs when the same music is moved to a different medium, e.g. from symphony orchestra to piano duet. In music, transcriptions are improved by recognition of forms and structures which are inherently characteristic of the medium being transcribed into. In the case of J these are hooks, forks, and sequences as rendered by atop. The fork is probably the single most important form. I find it best to think of it as a central operation applied following two outer transformations, both of which are performed on the combination of left and right arguments — Walter’s "invisible" data of which the J programmer must always be keenly aware. The first part of the previous sentence is an expression in words of the J definition of the fork f g h, namely:

   (x. f y.)  g  (x. h y.)

I want to base a second example for transcription on Alan Sykes’ article "Calculating Probabilities for Elementary Distributions", which also appeared in Vol. 12 no. 1. In Volume 12 No.2 I proposed the following alternative algorithm for calculating the logarithm of r!n:

[0]  Z ← n lnncr r
[1]  Z ← +/(⍟1+n - ⍳r) - ⍟⍳r←r⌊n-r

Examine first what this function does in terms of a simple example, say 6 lnncr 4. The first step is to transform r into (n-r) if the latter quantity is smaller, so in this case 4 is transformed into 2. The next step is to construct a numerator of two descending integers 6 5, and a denominator of two ascending integers 1 2.

The third stage is divide these by taking logs of each and subtracting, and the final stage is to sum these logs.

Looking at the first step, the data is r!n and this has to be transformed to (n-r)!r, so that the function min(r,n-r) can be applied. A fork structure is clearly recognisable here with min as the central operation. Of the two preliminary transformations, the rightmost is -, the leftmost is "take the right argument", and so the required fork is:

 k=.]<.-

The pattern "numerator divide-by-subtracting-logs denominator" is another clear fork, of which the central operation is well-known as a J adverbial form namely -&^. (see J-ottings 3 for details). We have hereby dealt with both occurrences of log in the APL line, as well as the subtraction. The next step is to seek the two transformations which give the numerator and denominator, bearing in mind that the J function is written in origin 1, whereas J respects only origin 0. Returning to the numerical example, 6 5 is 6 (right argument) - 0 1. -i. is a hook which forms the central operation of a fork, of which the left transformation is "right argument", and the right is the transformation already identified as k. Hence define

 g=.[(-i.)k

The transformation defining the denominator is even simpler. In the numerical example, the value required is simply 1 2, that is "increment by 1" (>:), following (that is, atop) i., following the transformation k. Hence define

 h=.>:@i.@k

The central fork is now defined, and it remains only to add the +/ in sequence:

 lnncr=.+/@(g(-&^.)h)

A possible objection to this transcription is that both g and h contain k as their rightmost verb which is both inefficient and inelegant. Whether there is a purely formal way of factoring the k’s is an interesting question beyond my powers of analysis. (Perhaps the generalisation of this problem could be suitable material for a Ph.D thesis!). Pragmatically, what has to be observed is that the core fork g(-%^.)h is itself the central operation in a broader fork whose right transformation is k, and whose left is "leave the left argument as it is". Thus the revised definition, following the factoring of k, is:

 g1=.-i.
 h1=.>:@i.@]
 lnncr1=.+/@( [ ( g1(-&^.)h1 ) k )

Of course it is possible to replace the definitions of g1, h1 and k directly to give the sort of one-liner effect which gave APL such a bad name in past times:

 lnncr2=.+/@([ ( (-i.) (-&^.) (>:@i.@]) ) k )

This is the sort of line which I imagine caused Dick Bowman in his review of J for Windows in the current "Quote Quad" to say that J looks ugly. I find it surprising that punctuation symbols which are totally inoffensive when sprinkled through text should be so unappealing when they appear in a concentrated stream. The moral is simple, namely to break down J verb definitions into bites of about eight characters or so — about the length of an English word which, even if previously unknown to the reader, can be readily assimilated in a single glance. I imagine that few would argue against the superiority of a suitably commented sequence of verbs which should make the algorithm completely clear on subsequent revisiting:

 k=.]<.-                   NB. transform r of nCr to n-r if smaller
 g1=.-i.                   NB. numerator is   n.(n-1). ...
 h1=.>:@i.@]               NB. denominator is 1. 2.  ...
 lnncr1=.+/@([(g1(-&^.)h1)k)  NB. sum logs following subtraction

As a final comment cap ([:) can be used instead of atop. The effect is to make the outer transformations on forks apply monadically. For example h1 above could be written equivalently as

 h1=.[:>: [:i.]

Whether atop or cap is preferable in such situations is a matter of personal taste. I am happy to think in terms of sequences passing results leftwards which produces the monadic effect in another way.

Returning to Walter’s example, there is a clear primary fork which adds the start value to all items in an arithmetic sequence of length n+1. What is added to a is a division of the range (b-a) by n, multiplied by 0,1, .. n+1, so choose the second major fork to have % as its central operation. The rest of the analysis (p and q below) is what is obtained as answers to the question: "What are the transformations required to produce the two parts of this quotient?". Here is my solution:

 p=.i.@>:@[                NB. 0,1, .. n
 q=.%-/@-                  NB. reciprocal of range
 r=.{.@]+p%q               NB. a + (0,1/n, .. 1)*range

Comparison with Walter’s version requires substituting for p and q in r, which gives:

 r1=.{.@]+(i.@>:@[%(%-/@-))

This has 22 characters as opposed to Walter’s 32, and there are only two references to [ and ], as opposed to five in Walter’s. The overall moral is: be fearless in transcribing from APL to J, but be sensitive also to the receiving medium. The question of formal analysis and manipulation of J expressions is intriguing, but hard!