Skip to content

J-ottings 14

Vector 14:2, page 38

programming techniques language design education

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

THINK, REASON, EXPRESS was Bill McLean’s plea as he struggled to understand the verb malt, standing for "merge alternate", which appeared in J-ottings 13. Here is how it was given there:

malt=./:@,&(i.@#){, NB. merge x. and y. using alternate elements

In Bill’s view, J strings with thirteen characters in the definition do not lend themselves to easy reading, a view with which I would concur! malt thus breaks what for me is an implicit rule that a tacit function definition which exceeds about seven to eight characters should be broken down into subsidiary verbs. At least this is a rule which should be observed if there is any expectation that the verb definition should be readily intelligible to a reader with a sound knowledge of the primitive J verbs. There is a close parallel with words in English. A new or neologised seven or eight letter word ought to be intelligible to, or at least pronouncable on sight by, a native speaker, whereas twelve or thirteen letters usually force such a reader to hesitate in piecing together the word’s basic construction. I excuse myself concerning malt because it was used as an auxiliary verb in solving a larger problem. However it furnishes a good illustration of the principle of "think, reason, express", and in my view thereby deserves further discussion.

First the problem itself is that of merging two vectors (not necessarily of equal length) with items taken alternately from each so that, for example, the merge of 1 2 3 4 and 5 6 is 1 5 2 6 3 4. "Think" and "reason" might appear to be synonyms — I use the former to describe understanding a problem in terms unrelated to the intended programming language, whereas the latter is about matching the details of the problem to, in this case, the constructs of J. A suitable set of synonyms might be "analyse, model, communicate".

So let’s think! It would be reasonable to glue the vectors together as 1 2 3 4 5 6 and then seek an algorithm for the ordered indices which define the merge. (No mention of J as yet!) Now let’s reason. "Glue" is modelled by "join" and indexing by "from". The latter is clearly the central operation, following two preliminary data transformations, one (on the right) a join, and the other (on the left) the algorithm which produces the correct indexes. This generic form is what I think Donald McIntyre refers to as "the ubiquitous fork". Since two of the present operations are primtives in J, encouragement can be taken from the fact that two-thirds of the work is already done:

malt=.mselect { ,

This leaves only the subordinate verb mselect to be designed. Once again, think. The result of gluing two index vectors of the same lengths as the data vectors is a vector containing somewhere within it two 0s which match the first pair of items in the merged vector, two 1s matching the next pair, and so on. Thus far no J. Now reason — gluing takes place following a preliminary transformation on each vector, the classic circumstance in which to use "with" (&). The transformation from vector to index vector is "tally then index", so define

ivector=.i.@#

and the gluing is then

ijoin=.,& ivector

The final step exploits the way in which grade-up works with ties, namely by counting them from left to right occurrences, which leads to the index selection verb

mselect=./:@ijoin

So combining everything, the analysis and subsequent modelling is expressed by

malt=.mselect { ,
mselect=./:@ijoin
ijoin=.,& ivector
ivector=.i.@#

With hindsight, J has proved an excellent medium in which to embody the principle of THINK, REASON, EXPRESS.

Of course, when malt becomes part of a larger algorithm it is not unreasonable to reduce the number of subordinate verbs in the manner given in J-ottings 13 and the start of this article. With this in mind, it is instructive to consider the merge algorithm given in J Phrases, viz:

merge=.1 :'/: @ /:@(x."_){,'

First this is an adverb, and secondly the definition is explicit. Nevertheless the final two-thirds are identical to malt, namely {,. As for the rest, the purpose is to generalise matters to allow the merge to be determined by a left argument such as 0 0 1 1 0 0 which would lead to a solution 1 2 5 6 3 4 for the example vectors above.

Next, consider the verbs over and by which are given as "black boxes" at an early stage of the Introduction and Dictionary:

over=.({.;}.)@":@,
by=.' '&;@,.@[,.]
'abc' by 1 2 3 4 over i.3 4
┌─┬─────────┐
│ │1 2  3  4│
├─┼─────────┤
│a│0 1  2  3│
│b│4 5  6  7│
│c│8 9 10 11│
└─┴─────────┘

both of which fall well outside my seven/eight character comprehensibility limit, and could be accused of giving at least an initial impression of what Don Mattern calls "comic book invective". It is instructive to reverse engineer these verbs into a "think, reason, express" sequence.

The objective of over is to attach numeric column headings to a matrix, and so ultimately each headed column has to be presented, suitably formatted, in the form of two vertically aligned boxes. Thus far, no J. Now reason. Join and format are primitives which should be executed in that order, hence

   join_fmtd=.":@,
   1 2 3 4 join_fmtd i.3 4
1 2  3  4
0 1  2  3
4 5  6  7
8 9 10 11
   $1 2 3 4 join_fmtd i.3 4
4 9

In J, when one or more of a pair of joined objects has rank more than 1, the objects are displayed in a vertical stack. Now exploit the knowledge that "link" (;) automatically boxes objects. In the present instance the column headers and table body should be boxed separately following the join, that is, two boxes are required, one for the head, and the other for the tail. This leads to another instance of a fork:

boxes=.{.;}.
boxes 1 2 3 4 join_fmtd i.3 4
┌─────────┬─────────┐
│1 2  3  4│0 1  2  3│
│         │4 5  6  7│
│         │8 9 10 11│
└─────────┴─────────┘

This operation must follow join_fmtd in sequence, leading to

over=.boxes @ join_fmtd
boxes=.{.;}.
join_fmtd=.":@,

The fact that the boxes are side by side rather than vertically stacked does not matter since the verb by will complete the alignment process.

The main purpose of by is to add the row headings which may be either character or numeric. This stage must deal with the extra complexity of the "top-left-corner cell" which characterises data presented with row and column headers. On the assumption that this cell is occupied with empty space, it should be boxed and appended to a second box containing the row headings, and the result of this appended columnwise to the result of over. Once again, this is all thinking/analysis, no J yet. Next the reasoning stage. The picture at the end of the thinking stage is two boxed two-item columns, the leftmost containing space and row headers, and the rightmost column headers and the table body. The correct display is an itemwise append of these objects which is given by append items ,. . The underlying generic form is the same as in malt, namely a fork whose central operation is "append items", and in which two-thirds of the work is done by primitives, leaving the enlargement of the row header outstanding as a subproblem:

by=.enlarge_rowhdr ,. ]

The enlargement of the row header with space is also modelled by append items ,. , (a simple join would result in an object whose tally is r+1 where r is the number of rows), and, as in over, the definition of link guarantees the required boxing. Itemwise appending should be done "with" link, that is using "with" (&) in its technical J sense:

space_append=.' '&;@,.
space_append 'abc'
┌─┬─┐
│ │a│
│ │b│
│ │c│
└─┴─┘

When space_append is integrated into the verb by, the row header becomes the left argument, and so enlarge_rowhdr is simply space_append sitting atop [ with the right argument not involved:

'abc'(space_append@[) i.3 4
┌─┬─┐
│ │a│
│ │b│
│ │c│
└─┴─┘

The expression of the reasoned definition is thus:

by=.space_append@[ ,. ]
space_append=.' '&;@,.

In the "black-box" definition of by, the implied bracketing is

over=.(({.;}.)@":)@,
by=.(' '&;@,.)@[),.]

on account of the rule that conjunctions have long left scope, that is, in the absence of parentheses, the occurrence of the first verb to the right of say @ forces a composition. In over as developed above, boxes sits atop the composition of join and format, whereas in the "black-box" version, the composition of boxes and format is atop join. Since these particular verbs obey an associative law, that is (f@g)@h is equivalent to f@(g@h), the two versions of over are functionally identical.