Skip to content

J-ottings 5: Append Items

Vector 11:4, page 27

language design education

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

The way in which data is structured constitutes a major difference between APL and J. At one time these differences were referred to as "floating" and "grounded" systems of nested arrays. Performing the activities of joining objects in the two languages is one of the best ways of highlighting these differences.

For definitiveness, suppose that a general instruction is given by a client to "join" two objects, one of which is to be represented by a vector and the other by a matrix. In J, there are eighteen potentially different ways in which a vector-matrix join can be interpreted, which doubles if the order vector/matrix is reversed. In other words there are no fewer than 35 ways in which the original loose specification can be misinterpreted!

To go back a step, one of the fundamental ways in which APL thinking must be adjusted for J concerns the manner in which arrays are perceived. In APL, an array is an arrangement of ×/⍴⍴a scalar items, whereas in J it is a sequence of items each of rank one less than the rank of a. The individual dimensions of an APL array are of equal importance in the sense that they can all be used as axis qualifiers. The dimensions of a J array however possess an inherent order of importance. Specifically, an APL matrix is a two-way-ordered collection of scalar items, a J matrix is a sequence of rows, a three-dimensional J array is a sequence of matrices and so on. The scalar items in an array are called 0-cells. k-cells (k>0) are counted from the inside out, so for a three-dimensional array, 1-cells are rows and 2-cells are matrices, whereas if k<0, (-1)-cells are matrices and (-2)-cells are rows.

These distinctions explain why J has two separate joining operations, namely append items (,), and append (,.). The essence of "append items" is that it raises the objects to be joined to a minimum common rank, if necessary using itemize (monadic ,:). Itemize involves adding shape value items of 1, an operation performed in APL by ,[⍳0]. For example if v=.i.3 and m=.i.3 4, v,m requires that v be made into a matrix of rank 1 3, whose single 1-cell is joined to the three 1-cells (rows) of m. If a=.i.3 2 4, v,a is obtained by first making v into a 1 1 3 array, then padding this to a shape of 1 2 4 which is then joined to the three 2-cells (matrices) of a, to give a result of shape 3 2 4.

"Append" on the other hand means join at the (-1)-cell level of the right argument, so with the values of the previous paragraph, v,.m means join each item of v to the corresponding row of m to obtain a matrix of shape 4 4. Similarly v,.a requires each item of v to be matched with the corresponding (-1)-cell item (that is matrix) of a to give a result of shape 3 3 4.

J provides a third joining primitive, namely laminate (,:). This works in a similar, but less restrictive, manner than laminate in APL. As with the latter, the effect on the shape vector is, in general, to add a new item to the shape vector of value 2.

Understanding how items can be joined in J thus amounts to appreciation of the three primitive operations, append items(,), append(,.), and laminate(,:). The rank conjunction (") provides separate possibilities for the left and right ranks up to the maximum ranks of the objects concerned. Suppose that the requirement referred to initially is to join a vector v and a matrix m in that order. There are six essentially different arguments for the rank conjunction, namely:

1 2 equivalent to omitting the rank conjunction altogether, which means apply maximum rank for both v and m. This is also equivalent to _ _ where _, standing for infinity, means "maximum rank";
1 or equivalently 1 1, meaning join the vector to the 1-cells, that is rows, of the matrix;
1 0 join the vector to the individual cells of the matrix;
0 2 join the individual cells of the vector to the entire matrix;
0 or equivalently 0 0, meaning join cell to cell. This is valid only if v is "row-compatible" with m, that is the number of items in v is equal to the number of rows in m;
0 1 join the cells of v to the matching rows of m — this also requires row-compatibility.

To exhibit the results of all the various possibilities in execution would require an unjustifiably large amount of printed space. It is more useful and practicable to give a table of APL2-equivalent expressions, in which, for consistency with J, index origin 0 is assumed. As the table shows, disclose is used extensively to provide the padding with fill items which occurs naturally with J.

rank   append items (,)        append (,.)            laminate (,:)
----   ----------------        -----------            -------------
nil    ⊃(⊂v),⊂[1]m        A    v,m               R    ⊃⊃(⊂v)(⊂[1]m)
1      ⊃(⊂v),¨⊂[1]m            ⊃⊃[0]v,¨¨⊂[0]m    C    ⊃⊃¨(⊂⊂v),¨⊂¨⊂[1]m
1 0    ⊃(⊂v),[0]¨m             ⊃⊃(⊂v),[0.5]¨m         ⊃(⊂v),[¯0.5]¨m
0 2    ⊃v,[0]¨⊂m               ⊃v,¨⊂m                 ⊃v,[¯0.5]¨⊂m
0      ⊃v,[0.5]¨⊂[1]m     R    ⊃v,[0.5]¨⊂[1]m    R    ,[⍳0]⊃v,[0.5]¨⊂[1]m   R
0 1    v,m                R    ⊃v,[0.5]¨⊂[1]m    R    ⊃v,[¯0.5]¨⊂[1]m       R

Notes: R requires row compatibility; C requires column compatibility; A simplifies to v,[0]m if APL row compatibility also present.

It may be helpful to record in a matching 3 by 6 table the shape vectors which result from executing the above expressions for the case v=.i.3 and m=.i.3 4.

             ,          ,.          ,:
           ----        ----        ----
1 2         4 4         3 5       2 3 4
1           3 7         n/a       3 2 4
1 0       3 4 4     3 4 3 2     3 4 2 3
0 2       3 4 4       3 3 5     3 2 3 4
0         3 4 2       3 4 2     3 4 2 1
0 1         3 5       3 4 2       3 2 4

For row-compatible v and m, the last two rows of the first table show that v,"0 m, v,."0 m and v,."0 1 m are identical. None of these pairs of expressions are equal in general for arguments of other ranks. Also "row compatibility" has to be generalised to "(-k)-cell compatibility" for objects of general rank where k is the smaller of the two object ranks. Thus to append a 3 by 4 matrix to a higher rank array (or vice versa) with rank conjunction 0, it is necessary that the shape vector of the array begins 3 4 ….

In APL2, variations on join are provided by using join with axis qualifier, and enclose with and without axis qualifier. The next table turns things round, and shows the J equivalents of the various APL2 alternatives:

              m not enclosed           m enclosed with axis        m enclosed
              --------------           --------------------        ----------
v not         v,m             C A0     (<"0 v),<"1 |:m      A0
enclosed                                                           (<"0 v),<m
              |:v,m           R A1     (<"0 v),<"1 m        A1

v             (<v),<"0 m        A0     (<v),<"1 |:m         A0
enclosed                                                           (<v),<m
              |:(<v),<"0 |:m    A1     (<v),<"1 m           A1

Notes: R join along axis 0 — requires APL row compatibility; C join along axis 1 (default) — requires APL column compatibility; A0 enclose along axis 0; A1 enclose along axis 1 (default).

Laminate is a less rich operation in APL2 than in J, since objects for APL lamination must be both rank and shape compatible. In the case of two matrices there are three possible laminations, for which the J equivalents are as follows:

m0,[-0.5]m1          m0,[0.5]m1          m0,[1.5]m1
m0,:m1               m0,:"1 m1           m0,:&.|:m1

Although the above tables cover only a subset of the possible instances of join (namely those for vector left/matrix right argument), the principles involved apply equally to cases of other ranks, and in practice this way of working is preferable to using general formulae involving shapes and ranks of the arguments.