APL and J (5): Operators – Inner Product, Each, Commute, Compose
Vector 14:1, page 129
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 129
This series of articles entitled APL and J will try to explain the J way of thinking, by contrasting APL and J coding examples. The term APL will be used generically to refer to facilities found in most commercial implementations of APL.
To conclude the discussion of APL’s operators, we now look at inner product, each, and two operators found only in Dyalog APL: commute and compose.
Housekeeping¶
As usual, we use the letters V, M, and A for vectors, matrices and arrays of any dimension, optionally followed by numbers to denote their shape. Thus V for any vector, V5 for a vector of length 5, M45 for a 4-by-5 matrix, A245 for a 2-by-4-by-5 array. Where actual values are required, V5 will be defined as ⍳5, M45 as 4 5⍴⍳20.
APL terminology will be used, except where J terminology is required for clarity. Here, ⎕io←1 is assumed throughout.
Inner Product¶
Inner product in APL is called dot product in J, and both are denoted by the dot symbol, f.g for functions f and g. As may be expected, f is applied with reduce in APL, but not in J, where it must be given explicitly if required. Thus the following are equivalent:
(Note that in J, a space is required before the dot - since otherwise the dot would be read as part of the previous symbol.)
Does f.g in APL always give the same result as f/ . g in J? Yes - if we look at typical APL uses of inner product, such as +.× or ∧.=. However, the definitions are quite different, and nicely illustrate the two approaches to array operations. J also allows more possibilities, though I have yet to use a dot product in J that could not be written in APL.
In the following discussion, we apply dot product as: X f.g Y where the shapes of X and Y are SX and SY.
In APL, ¯1↑SX must match 1↑SY, and the computation is made by splitting X into vectors along the last axis, and splitting Y into vectors along the first axis, and applying g followed by f/ to each combination of vectors from X and Y, and enclosing the individual results.
Since the individual results are enclosed, they do not contribute to the result shape, which must therefore be (¯1↓SX),1↓SY. The computation for each pair of vectors x from X and y from Y is:
For example, M22 +.× M23 is evaluated as:
(⊂ +/ 1 2 ר 1 4) (⊂ +/ 1 2 ר 2 5) (⊂ +/ 1 2 ר 3 6)
(⊂ +/ 3 4 ר 1 4) (⊂ +/ 3 4 ר 2 5) (⊂ +/ 3 4 ר 3 6)
and assembled into the matrix:
In J, the right argument Y is not split up at all, while the left argument X is split up into cells of rank 1 plus the left rank of g (you can think of lists of left argument cells). The number of items in the left argument cells must match the number of items in Y. Therefore, in the common case where the left rank of g is 0 (e.g. where g is * or =), X is split up into vectors along the last axis just as in APL.
For example, M22 +/ . * M23 is evaluated as:
which is:
which is then assembled into the matrix:
Note that this makes use of J’s ability to multiply a vector by a matrix or arbitrary array, using prefix agreement.
The shape of the result of dot product in J is the shape of the frame of X (i.e. SX less the shape of the cells used in the dot product), catenated with the shape of the result cells. In the following example, let f=. (i.10)"_ be the function that returns the list i.10, and g=. 0"2 has left rank 2, then:
The key changes J makes are: the right argument Y is not split up at all; the left argument X is split up according to the left rank of g; and the results of each computation are not enclosed, but follow normal assembly rules.
Determinant and Permanent¶
In J, the dot product can be used monadically, when it is the determinant function, thus:
If + is used instead of -, the function is the permanent. Any other verb could be used in place of - or +, but I don’t know of a useful example!
Each¶
The APL expression f¨ (f each) means apply f to each scalar in an array, opening each scalar before applying the function, and enclosing the result. Since each result is enclosed, it must be that (⍴f¨X)≡⍴X.
J has no built-in equivalent to the APL each operator. However, one can be constructed as a special case of the under conjunction &. , and this is defined in the J standard library. Given verbs u and v where the inverse v⁻¹ is defined, then u&.v is equivalent to v⁻¹ u v.
Now, if v is the verb > (open), then its inverse is < (box), so that f&.> means open, apply f, then box, which is exactly the same as APL’s f¨, for example:
So if we define each to be &.> , then this is an adverb whose definition and behaviour is exactly like APL’s each operator:
J’s rank operator " and APL’s each operator ¨ are notationally similar, and should not be confused. For further discussion, see the first article in this series (Vector 13.1 pp. 96–101, July 1996).
Commute¶
In Dyalog APL, the commute operator ⍨ creates a new function which reverses its arguments, so that X f⍨ Y is equivalent to Y f X, for example:
The corresponding operator in J is ~ , which reverses the arguments when the resulting function is used dyadically:
Where the resulting function is used monadically, the argument is used as both left and right argument, so that u~ y is y u y, for example:
Why bother with such a trivial operator? For one thing because it simplifies expressions by reducing the need for parentheses, and for another because it is useful in constructing tacit definitions, for example:
Compose¶
In Dyalog APL, the compose operator ∘ takes two arguments which are either a function and one of its arguments; or both functions.
In the first case, an argument is bound to a dyadic function, creating a new monadic function:
The J equivalent is & , for example:
Joining a function with one of its arguments to create a new function is called currying in honour of Haskell Curry.
In the second case, two functions are composed, creating a new function f ∘ g which applies first g, then f. The function g is used monadically, while f may be monadic or dyadic.
Where f is monadic, f ∘ g X is equivalent to f g X, for example:
The J equivalent for the monadic case is given by @ or & (either will do), e.g.
Where f is dyadic, X f ∘ g Y is equivalent to X f g Y, for example:
This composition is the same as J’s hook:
This example makes it clear that a hook could have been constructed as a normal operator, rather than a train of 2 verbs. The reason for making it a train is that it complements the fork, which is a train of 3 verbs, allowing resolution of trains of any length.
Note that where f is dyadic, then f ∘ g is not the same as J’s f @ g or f & g. In these cases:
Reference¶
Iverson, K.E. J Introduction and Dictionary, Iverson Software, 1996