[Home]

Table of contents


Learning J

Monads and dyads

In the last page we learned about some nice things to expect from J. Now, nothing comes without a price. A language needs to restrict the freedom of a programmer in various ways to make the good things possible. The astute reader may have already discerned that the idea of applying a function to specific level of a tree is meaningful only if the tree is balanced (i.e., the number of children of a node equals the number of chidren for its siblings. This is the first restriction J puts. To ensure this restriction for the outcome of application of a function we need yet another restriction: the output shape of a function must depend on only the input shape (and not on the input values).
The shape of the green triangle determines the shape of purple triangle

Here is the next one: a function can have either one or two arguments. In operation parlance, these are called unary and binary operators. But J call them, respectively, monads and dyads. We have already learned about how monads behave. Dyads add a second level of complication.

Dyads

Recall that a single application of $f$ to $x$ may involve implicit loops to be launched causing multiple invocations. For example, if $x$ is a 1-dimensional array of length 10, then $sin(x)$ is a single application, but actually consists of 10 invocations. The specified rank determines how an application is split up into invocations.

The situation is similar for dyads. We have two ranks, one for each argument. So the two trees may be split up like these:
Notice that the top parts (i.e. the frames) of the two trees are different. So we cannot proceed to combine the gren and purple parts with $f$ immediately. However, the frame of the second tree starts out like that the first tree (i.e., the red parts are the same). So the second tree may "sprout" to match the shape of the first:
Each leaf node "sprouts" by replicating
Now we may apply $f.$ If a purple tree and a green tree are combined by $f$ into a blue tree:
then the final result will be:

Right-to-left

Since functions are either monads or dyads, so they are much like unary and binary operations that we learn in school. Indeed, J uses exactly the same syntax. Thus, instead of writing $f(x,y)$ we write x f y. Similarly, $f(x)$ is f x. In arithmetic, we have two types of minuses, unary and binary, much to the confusion of kids. J keeps up the tradition (both of arithmetic and of confusion) by allowing a same symbol to denote a monad as well as dyad. Usually, the monad and the dyad denoted by the same symbol are related, though this is not a requirement.

With all functions being written as binary and unary operators, it is no waonder that J has large number of these. So to keep the order of evaluation unambiguous we need to either put lots of parentheses or we need to have some convention like precedence and associativity. Even with as few operations as just +, -, * and /, untrained kids find it difficult to remember rules of precedence. You can imagine how much more confusing it is going to be with many more operators. Well, J has solved the problem by doing away with precedence altogether (and thus confusing trained adults). The J dictum is "always proceed from right to left". Before you and wonder about why we should proceed in this "reverse" way, and suspect some Arabic influence, let me point out that this is how we do it in math most of the time: $(f\circ g)(x) = f(g(x))$ means apply $g$ first, and then $f.$

Cryptic symbols

J justly prides itself on smart ways of combining existing operation to produce new ones. This is much like the way modern man writes numbers as opposed to ancient Romans. Ancient Romans assigned symbols more or less arbitrily to whatever number they needed, like I, V, X and C. But modern man has a systematic approach using a radix and expressing everything by combining powers of 10. If you want to express a hundred, the ancient Roman just used a letter C, while modern man would first express the number as square of 10, and notice that it is
1$\times 10^2+$ 0$ \times 10^1 + $0$\times 10^0.$
The ancient Roman must have thought us crazy that we need so much math to just express a number. But of course we know the advantage. However, even we would be driven crazy if we have to write the above expression in full everyime we need to express a hundred. So we need an abbreviation: we have (arbitrary) symbols for all the possible coefficients (0,1,2,...,9) and then simply list the coefficients side by side. Is it really simple? Yes, when you see that even a kid can do it. No, when we remember that it took centuries for this system to evolve.

Well, that is basically what happens for J also. J has a clever way of expressing many things by combining some basic objects. Arbitrary names of assigned to only the basic entities, and all other derived entities are expressed in their terms. Without abbreviations, however, this would not be of much practical value. So J uses very short names for its basic functions (at most two characters).

Forks and compositions

We have learned about forks and compositions in the last page. These are ways to combine existing functions to create new functions. At first glance they may appear to be merely some convenient shortcuts. But actually they play an important role in J. We shall discuss this now.

Monadic case

Here is a quick reminder for the monadic case (y denotes the argument): The power of these abbreviations comes from the fact that since all J functions are either mondas or dyads, hence the inner nodes of any syntax trees must have degree either 1 or 2:
0, 2, 4, 7 are dyads, rest are monads
So the tree may be constructed by just piecing together forks and compositions. For each dyad, we need a fork, and for each monad a composition. For the example shown above we have (using the right-to-left compuation order of J):
1 0 3 2  5@(8 7 9) 4 6
It might look like magic. It is! But just like all magics in real life, there is a way to practice them. The tree technique is one such.

This example, however, represents the situation where all the leaf nodes correspond to monads, e.g., in $\sin y + \cos y$ the leaf nodes are $\sin$ and $\cos$, both monads.

Now, that may not always be the case, because
  1. a leaf node may be just some constant, as in $5 + \sin y$ the leaf node for 5 is holding a constant.
  2. A leaf node may be $y$ itself, as in $y -\sin y.$
J provides convenient techniques to cover these in the case where the troublesome leaf node is the left child of its parent:
  1. If the leaf node contains a constant, then just write it, and J will accept it as a constant function. For example, (5+sin) is a valid fork.
  2. In the second case, where the a leaf node contains $y$, there are two possible solutions. You may imagine $y$ as $id(y)$ where $id$ is the identity function. For instance, $y-\sin y$ becomes $id(y)-\sin y,$ which is a fork. This works irrespective of whether the $y$ occurs in the left or the right child. However, if it occurs in the left child, then there is a shortcut: just omit the $y$, and J will understand. With this, $y-\sin y$ becomes (-sin). Such lop-sided forks are called hooks in J.

In the above solutions, J shows a bias in favor of the left child. This may bother you. If the parent is a commutative operation, then the children may be swapped (e.g., $\sin(y) + 3$ may be written as $3+ \sin(y)$), but what if it is not? Don't worry. J has got you covered there too! J can swap the children of any dyad by using a ~. Thus, 4 -~ 5 is the same as 5 - 4. If you want to express $\sin(y)-5$ using a fork, then sin - 5 won't work, but 5-~sin will. Similarly, $\sin(y)-y$ may be abbreviated to the hook -~sin.

This discussion was about the monadic behaviour of forks. Similar ideas hold for dyads as well.

Dyadic case

As in the monadic case, any syntax tree may be built out of these. If the leaf nodes are all dyadic, and expect the same arguments, then everything is fine. However, that need not always be the case. Some of them may be monadic, or need a constant argument, or only $x$ or only $y.$ These are tackled as follows.

Tacit programming

It is possible to build any syntax tree of dyads and monads using forks (hooks) and compositions. This is an example of what is called tacit programming in J: creating new functions by combining existing functions via operators without explicitly mentioning the arguments.

A powerful feature of J as it is, one must not go overboard with it. Creating a huge syntax tree by repeated use of forks (hooks) and compositions is not desirable from the viewpoint of readability. But tacit programming helps a lot for smaller trees.