<< >> Contents Help

# 5. Loopless Code I—Verbs Have Rank

Most J programs contain no loops equivalent to while and for in C. J does contain while. and for. constructs, but they carry a performance penalty and are a wise choice only when the body of the loop is a time-consuming operation. You are just going to have to learn to learn to code without loops.

I think this is the most intimidating thing about learning J—more intimidating even than programs that look like a three-year-old with a particular fondness for periods and colons was set before the keyboard. You have developed a solid understanding of loops, and can hardly think of programming without using them. But J is a revolutionary language, and all that is solid melts into air: you will find that most of your loops disappear altogether, and the rest are replaced by small gestures to the interpreter indicating your intentions.

Come, let us see how it can be done. I promise, if you code in J for 6 months, you will no longer think in loops, and if you stay with it for 2 years, you will see that looping code was an artifact of early programming languages, ready to be displayed in museums along with vacuum tubes, delay lines, and punched cards. Remember, in the 1960s programmers laughed at the idea of programming without gotos!

You are not used to classifying loops according to their function, but I am going to do so as a way of introducting J's primitives. We will treat the subject of loopless iteration in 6 scattered chapters, showing how to replace different variants of loops:

1. Loops where each iteration of the loop performs the same operation on different data;

2. Loops that apply an operation between all the items of the array, for example finding the largest item;

3. Loops where the operation to be performed on each cell is different;

4. Loops that are applied to regularly-defined subsets of the data;

5. Loops that are applied to subsets of the data defined irregularly;

6. Loops that accumulate information between iterations of the loop.

The simplest case is the most important, and we start with a few experiments.

Examples of Implicit Loops

2 + 3 4 5

5 6 7

The verb dyad + is addition, and we have our first example of an implicit loop: the left argument 2 was added to each atom in the right argument.

1 2 3 + 4 5 6

5 7 9

And look! If each operand is a list, the respective items are added. We wonder if the behavior of 2 + 3 4 5 was because items of the shorter operand are repeated cyclically:

1 2 + 4 5 6

|length error

| 1 2 +4 5 6

Evidently not. A 'length error' means that the operands to + did not 'agree' (and you get an error if you try to add them). We will shortly understand exactly what this means.

i. 2 3

0 1 2

3 4 5

A reminder of what monad i. does.

0 100 + i. 2 3

0 1 2

103 104 105

Whoa! The atoms of the left operand were applied to rows of the right operand. Interesting. This seems to be some kind of nested implicit loop.

Let's learn a couple of more verbs, monad #. and monad #: . Monad #: creates the binary representation of an integer (i. e. a list of 0s and 1s), and monad #. is its inverse, creating the integer from the binary representation. For the longest time I couldn't remember which was which, but at last I saw the mnemonic: the verb with the single dot (#.) creates an atom from a list; the verb with multiple dots (#:) creates a list from an atom:

#: 5

1 0 1

#. 1 0 1

5

Yes, they seem to perform as advertised. They can be applied to arrays:

]a =. #: 5 9

0 1 0 1

1 0 0 1

Look: the result is not a rank-1 list, but rather a rank-2 array, where each item has the binary representation of one operand value (and notice, an extra leading zero was added to the representation of 5). The little trick with ]a =. will be explained later, but for now just think of ]a =. as 'assign to a and display the result'. With a assigned, we have:

#. a

5 9

This seems to be the desired result, but on reflection we are puzzled: how did the interpreter know to apply #. to each 1-cell rather than to each 0-cell? Contrast this result with the result of the verb monad +:, which means 'multiply by 2':

+: a

0 2 0 2

2 0 0 2

Evidently the verbs themselves have some attribute that affects the rank of cell they are applied to. It's time for us to stop experimenting and learn what that attribute is.

The Concept of Verb Rank

Every verb has a rank—the rank of the cells to which it is applied. If the rank of the verb's operand is smaller than the rank of the verb, the verb is applied to the entire operand and it is up to the author of the verb to ensure that it produces a meaningful result in that case.

Dyads have a rank for each operand, not necessarily the same.

A verb's rank can be infinite (_), in which case the verb is always applied to the operand in its entirety. In other words, if a verb has infinite rank for an operand, that operand is always processed as a single cell (having the rank of the operand).

If you don't know the rank of a verb, you don't know the verb. Using a verb of unknown rank is like wiring in a power-supply of unknown voltage—it will do something when you plug it in; it might even work; but if the voltage is wrong it will destroy what it's connected to. Avoid embarrassment! Know the rank of the verbs you use.

The definition page of each J verb gives the ranks of the verbs defined on the page, right at the top of the page after the name of the verb. Since most pages define both a monad and a dyad, you will usually find 3 numbers: the first is the rank of the monad, the other two are the left and right rank of the dyad. For example, click up the page for #: and you will see

#: _ 1 0

which means that monad #: has infinite rank, while dyad #: has left rank 1 and right rank 0. For any verb, including user-written verbs, you can ask the interpreter the rank by typing verbname b. 0 :

#: b. 0

_ 1 0

Verb Execution—How Rank Is Used (Monads)

The implicit looping in J results from the interplay of verb rank and noun rank. For monads, it goes like this:

1. Figure out the rank r of the cells that will be operated on; this will be the smaller of rank of the verb and the rank of the operand. This rule applies even if the verb has infinite rank: r will be the rank of the operand, which is another way of saying that the verb applies to the operand in its entirety.

2. Find the frame f of the operand with respect to cells of rank r.

3. Think of the operand as an array with shape f whose items are cells of rank r. Apply the verb to each r-cell, replacing each cell with the result of the verb. Obviously, this will yield an array of shape f whose items have the shape of the result of applying the verb to an r-cell.

Let's look at some simple examples:

i. 2 2

0 1

2 3

This will be the right operand.

+: i. 2 2

0 2

4 6

The steps to get this result are:

The verb rank is 0 and the noun rank is 2, so we will be applying the verb to 0-cells. The frame f is 2 2
Think of the operand as a 2×2 array of 0-cells: |
0 | 1
2 | 3
The verb is applied to each cell: |
0 | 2
4 | 6
Since each result is an atom, i. e. a 0-cell, the result is a 2×2 array of 0-cells, i. e. an array of shape 2 2 | 0 2 4 6

Figure 1. Execution of +: i. 2 2

Another example:

]a =. 2 2 4 $ 0 0 1 1 0 0 0 1 0 1 0 0 0 0 1 0

0 0 1 1

0 0 0 1

0 1 0 0

0 0 1 0

This is a rank-3 array.

#. a

3 1

4 2

The verb rank is 1 and the noun rank is 3, so we will be applying the verb to 1-cells. The frame f is 2 2
Think of the operand as a 2×2 array of 1-cells: |
0 0 1 1 | 0 0 0 1
0 1 0 0 | 0 0 1 0
The verb is applied to each cell: |
3 | 1
4 | 2
Since each result is an atom, i. e. a 0-cell, the result is a 2×2 array of 0-cells, i. e. an array of shape 2 2 | 3 1 4 2

Figure 2. Execution of #. 2 2 4 $ 0 0 1 1 0 0 0 1 0 1 0 0 0 0 1 0

Controlling Verb Execution By Specifying a Rank

The implicit loops we have used so far are interesting, but they are not powerful enough for our mission of replacing all explicit loops. To understand the deficiency and its remedy, consider the new verb monad +/, which creates the total of the items of its operand (just think of it as 'monad SumItems'):

+/ 1 2 3

6

The result was 1 + 2 + 3, as expected.

i. 2 3

0 1 2

3 4 5

+/ i. 2 3

3 5 7

The result was 0 1 2 + 3 4 5, as expected (remember that the items are added, and the items of i. 2 3 are 1-cells). Adding together a pair of 1-cells adds the respective atoms, as we will soon learn.

This application of monad +/ to a rank-2 array corresponds to the C code fragment:

for(j = 0;j<3;++j)sum[j] = 0;

for(i = 0;i<2;++i)

for(j = 0;j<3;++j)sum[j] += array[i][j];

Suppose we wanted to add up the items of each row, as in the C code fragment

for(i = 0;i<2;++i) {

sum[i] = 0;

for(j = 0;j<3;++j)sum[i] += array[i][j];

}

to produce the result 3 12? How can we do it in J? What we have learned so far is not enough, but if we had a way to make monad +/ apply to 1-cells—if we could make monad +/ have rank 1—our problem would be solved: the implicit looping would cause each row to be summed and the results collected.

You will not be surprised to learn that J does indeed provide a way to apply monad +/ on 1-cells. That way is the rank conjunction " .

We will learn all about conjunctions later on—the syntax is a little different than for verbs—but for now, we'll try to understand this " . It's used like this:

u"n

to produce a new verb that is u applied to n-cells individually. This is a simple idea, but its ramifications spread wide. As a first example:

+/"1 i. 2 3

3 12

This is what we were looking for. It happened this way:

The verb rank is 1 and the noun rank is 2, so we will be applying the verb to 1-cells. The frame f is 2
Think of the operand as a list of 2 1-cells: |
0 1 2 | 3 4 5
The verb monad +/ is applied to each cell: |
3 | 12
Since each result is an atom, i. e. a 0-cell, the result is a list of 2 0-cells, i. e. an array of shape 2 | 3 12

Figure 3. Execution of +/"1 i. 2 3

Examples Of Verb Rank

Here are some more examples using a rank-3 array as data:

i. 2 3 4

0 1 2 3

4 5 6 7

8 9 10 11

12 13 14 15

16 17 18 19

20 21 22 23

+/"1 i. 2 3 4

6 22 38

54 70 86

The verb rank is 1 and the noun rank is 3, so we will be applying the verb to 1-cells. The frame f is 2 3
Think of the operand as a 2×3 array of 1-cells: |
0 1 2 3 | 4 5 6 7 | 8 9 10 11
12 13 14 15 | 16 17 18 19 | 20 21 22 23
The verb monad +/ is applied to each cell: |
6 | 22 | 38
54 | 70 | 86
Since each result is an atom, i. e. a 0-cell, the result is a 2×3 array of 0-cells, i. e. an array of shape 2 3 | 6 22 38 54 70 86

Figure 4. Execution of +/"1 i. 2 3 4

+/"2 i. 2 3 4

12 15 18 21

48 51 54 57

The verb rank is 2 and the noun rank is 3, so we will be applying the verb to 2-cells. The frame f is 2
Think of the operand as a list of 2 2-cells: |
0 1 2 3 4 5 6 7 8 9 10 11 | 12 13 14 15 16 17 18 19 20 21 22 23
The verb monad +/ is applied to each cell. As we have learned, this sums the items, making each result a rank-1 list |
12 15 18 21 | 48 51 54 57
Since each result is a rank-1 list, i. e. a 1-cell, the result is a list of 2 1-cells, i. e. an array of shape 2 4 | 12 15 18 21 48 51 54 57

Figure 5. Execution of +/"2 i. 2 3 4

+/"3 i. 2 3 4

12 14 16 18

20 22 24 26

28 30 32 34

The verb is applied to the single 3-cell. Its items, which are 2-cells, are added, leaving a single 2-cell as the result.

How about i."0 (2 2 2)—can you figure out what that will produce? (Notice I put parentheses around the numeric list 2 2 2 so that the rank 0 wouldn't be treated as part of the list)

The verb rank is 0 and the noun rank is 1, so we will be applying the verb to 0-cells. The frame f is 3
Think of the operand as a list of 3 0-cells (i. e. atoms): |
2 | 2 | 2
The verb monad i. is applied to each cell: |
0 1 | 0 1 | 0 1
Since each result is a list, i. e. a 1-cell, the result is a list of 3 1-cells each with shape 2, i. e. an array of shape 3 2 | 0 1 0 1 0 1

Figure 6. Execution of i."0 (2 2 2)

i."0 (2 2 2)

0 1

0 1

0 1

If you worked through that, it might have occurred to you that the shape of each result cell depended on the value of the operand cell, and that if those cells had not been identical, there would be some rough edges showing when it came time at the end to join the dissimilar result cells together. If so, full marks to you! That can indeed happen. If it does, then just before the cells are joined together to make the final result, the interpreter will bulk up the smaller results to bring them up to the shape of the largest. First, if the ranks of the results are not identical, each result will have leading axes of length 1 added as needed to bring all the results up to the same rank (e. g. if one result has shape 2 5 and another has shape 5, the second will be converted to shape 1 5, leaving the data unchanged). Then, if the lengths of the axes are not identical, the interpreter will extend each axis to the maximum length found at that axis in any result: this requires adding atoms, called fills, which are always 0 for numeric results and ' ' for literal results. Example:

i."0 (0 1 2 3)

0 0 0 NB. original result was empty list; 3 fills added

0 0 0 NB. original result was 0; 2 fills added

0 1 0 NB. original result was 0 1; 1 fill added

0 1 2 NB. this was the longest result, no fill added

fndisplay—A Utility for Understanding Evaluation

J contains a script that we will use to expose the workings of evaluation. You define verbs which, instead of operating on their operands, accumulate character strings indicating what operations were being performed. This gives you a way of seeing the operations at different cells rather than just the results.

Start by loading the script:

load 'system/packages/misc/fndisplay.ijs'

Then, select the type of display you want. We will be using

setfnform 'J'

Then, give the names of the verbs you want to use. If you want to assign a rank, you may do so by appending "r to the name:

defverbs 'SumItems plus"0'

Optionally, define any names you want to use as nouns. The value assigned to the noun is the noun's name:

defnouns 'x y'

You are free to use other nouns in expressions, but they will be replaced by their values.

With these definitions, you can explore J's evaluations:

x (plus) y

+--------+

|x plus y|

+--------+

The result of the evaluation is a description of the evaluation that was performed. The result is displayed in a box so that a sentence with multiple evaluations shows each in its proper place:

SumItems"1 i. 2 3

+--------------+--------------+

|SumItems 0 1 2|SumItems 3 4 5|

+--------------+--------------+

Here we see that SumItems was applied twice, once on each 1-cell.

fndisplay cannot produce a valid result in cases where the rank of a verb is smaller than the rank of the result-cells of the preceding verb, because then the operation would be performed on part of a result cell, and the result cell is just a descriptive string which cannot be meaningfully subdivided.

In this book, if we give an example that starts with defverbs it is implied that load fndisplay and setfnform 'J' have been executed.

If you prefer to see the order of evaluation expressed in functional form like that used in C, you may issue setfnform 'math' before you execute your sentences:

setfnform 'math'

1 plus 2 plus y

+-----------------+

|plus(1,plus(2,y))|

+-----------------+

Negative Verb Rank

Recall that we defined the _1-cell of a noun n to be the cells with rank one less than the rank of n, and similarly for other negative ranks. If a verb is defined with negative rank r, it means as usual that the verb will apply to r-cells if possible, but with r negative the rank of those r-cells will depend on the rank of the operand. After the first step of processing the verb, which decides what rank of cell the verb will be applied to, verbs with negative rank are processed just like verbs of positive rank.

+/ "_1 i. 3

0 1 2

Here the _1-cells are atoms, so applying monad SumItems on each one has no effect.

+/ "_1 i. 2 3

3 12

The operand has rank 2, so this expression totals the items in each 1-cell.

+/ "_2 i. 2 2 3

3 12

21 30

The operand has rank 3, so this totals the items in each 1-cell, leaving a 2×2 array of totals.

Verb Execution—How Rank Is Used (Dyads)

We are at last ready to understand the implicit looping that is performed when J processes a dyadic verb. Because a dyadic verb has two ranks (one for each operand), and these two ranks interact with each other as well as the ranks of the operands, you should not read further until you thoroughly understand what we have covered already.

We have learned that the rank conjunction u"n is used to specify the rank of a verb. Since each verb has the potential of being invoked monadically or dyadically, the rank conjunction must specify the ranks for both valences. This requires 3 ranks, since the monad has a single rank and the dyad a left and a right rank. The ranks n may comprise from 1 to 3 items: if 3 ranks are given they are, in order, the monad's rank, the dyad's left rank, and the dyad's right rank. If two ranks are given, the first is the dyad's left rank and the second is used for the dyad's right rank and the rank of the monad. If there is only one item in the list, it is used for all ranks. So, v"0 1 has monad rank 1, dyad left rank 0, and dyad right rank 1 . As usual, J primitives themselves have the ranks shown in the Dictionary.

Processing of a dyad follows the same overall plan as for monads, except that with two operands there are two cell-sizes (call them lr and rr) and two frames (call them lf and rf). If the left and right frames are identical, the operation can be simply described: the lr-cells of the left operand match one-to-one with the rr-cells of the right operand; the dyad is applied to those matched pairs of cells, producing a result for each pair; those results are collected as an array with frame lf. We illustrate this case with an example:

(i. 2 2) + i. 2 2

0 2

4 6

The verb has left rank 0, and the left operand has rank 2, so the operation will be applied to 0-cells of the left operand. The verb has right rank 0, and the right operand has rank 2, so the operation will be applied to 0-cells of the right operand. The left frame is 2 2, the right frame is 2 2 .
Think of the left operand as a 2×2 array of 0-cells, and the right operand as a 2×2 array of 0-cells: |
0 | 1
2 | 3
0 | 1
2 | 3
The corresponding left and right operand cells are paired: |
0 | 0
1 | 1
2 | 2
3 | 3
The operation dyad + is performed on each pair of cells: |
0 | 2
4 | 6
Since each result is an atom, and the frame is 2 2, the result is an array with shape 2 2 | 0 2 4 6

Figure 7. Execution of (i. 2 2) + i. 2 2

Using fndisplay, we have

load'system\packages\misc\fndisplay.ijs'

setfnform 'J'

defverbs 'plus"0'

(i. 2 2) plus i. 2 2

+--------+--------+

|0 plus 0|1 plus 1|

+--------+--------+

|2 plus 2|3 plus 3|

+--------+--------+

As you can see, we were correct when we asserted that the sum of two cells of the same shape is taken by adding their respective items.

Concatenating Lists: Dyad , (Append)

For a second example we will introduce a new verb, dyad , (the verb is the comma character). x , y creates an array whose leading items are the items of x and whose trailing items are the items of y; in other words, it concatenates x and y .

1 2 3 , 6

1 2 3 6

1 2 3 , 1 2 3

1 2 3 1 2 3

4 , 6

4 6

1 2 3 , 0$6

1 2 3

(i. 2 3) , (i. 3 3)

0 1 2

3 4 5

0 1 2

3 4 5

6 7 8

In the last example the items of x and y are 3-element lists, so x , y is a list of 3-element lists, containing the items of x followed by the items of y .

Dyad , has infinite rank, which means that it applies to its operands in their entirety and so its detailed operation is defined not by the implicit looping we have been learning, but instead by the definition of the verb in the Dictionary. The discussion given above describes the operation of dyad , when the operands have identically-shaped items (items, mind you—the shapes of x and y may differ, if one has more items than the other). In a later chapter we will learn about x , y when the items have different shapes; for now we will be dealing with operands that are scalars and lists, for both of which the items are scalars.

Now see if you can figure out what (i. 3 3) ,"1 0 i. 3 will do before reading the explanation that follows:

The verb (dyad ,"1 0) has left rank 1, and the left operand has rank 2, so the operation will be applied to 1-cells of the left operand. The verb has right rank 0, and the right operand has rank 1, so the operation will be applied to 0-cells of the right operand. The left frame is 3, the right frame is 3 .
Think of the left operand as a list of 3 1-cells, and the right operand as a list of 3 0-cells: |
0 1 2 | 3 4 5 | 6 7 8
0 | 1 | 2
The corresponding left and right operand cells are paired: |
0 1 2 | 0
3 4 5 | 1
6 7 8 | 2
The operation dyad , is performed on each pair of cells: |
0 1 2 0 | 3 4 5 1 | 6 7 8 2
Since each result is a 1-cell of shape 4, and the frame is 3, the result is an array with shape 3 4 | 0 1 2 0 3 4 5 1 6 7 8 2

Figure 8. Execution of (i. 3 3) ,"1 0 i. 3

defverbs 'comma'

(i. 3 3) comma"1 0 i. 3

+-------------+-------------+-------------+

|0 1 2 comma 0|3 4 5 comma 1|6 7 8 comma 2|

+-------------+-------------+-------------+

(i. 3 3) ,"1 0 i. 3

0 1 2 0

3 4 5 1

6 7 8 2

When Dyad Frames Differ: Operand Agreement

The processing of dyads has an extra step not present for monads, namely the pairing of corresponding cells of the left and right operands. As long as the frames lf and rf are the same, as in the examples so far, this is straightforward. If the frames are different, J may still be able to pair left and right cells, using another level of implicit looping, one that provides considerable additional programming power. The formal description that follows is not easy to follow—you might want to skim over it and read it in detail after you have studied the examples that follow.

J requires that one of the frames be a prefix of the other (if the frames are identical, each is a prefix of the other and all the following reduces to the simple case we have studied). The common frame cf is the part of the frames that is identical, namely the shorter of the two frames; its length is designated rcf. If we look at the cells of the operands relative to this common frame (i. e. the (-rcf)-cells), we see that for the operand with the shorter frame, these cells are exactly the rank that will be operated on, while for the operand with the longer frame, each (-rcf)-cell contains multiple operand cells.

First, the (-rcf)-cells of the two operands are paired one-to-one (because they have the same frame), leaving each shorter-frame operand cell paired with a longer-frame (-rcf)-cell. Then, the longer-frame (-rcf)-cells are broken up into operand cells, with each operand cell being paired with a copy of the shorter-frame operand cell that was paired with the (-rcf)-cell (an equivalent statement is that the cells of the shorter-frame operand are replicated to match the surplus frame of the longer-frame operand). This completes the pairing of operand cells, and the operation is then performed on the paired operand cells, and collected using the longer frame. Maybe some examples will help.

100 200 + i. 2 3

100 101 102

203 204 205

The verb (dyad +) has left rank 0, and the left operand has rank 1, so the operation will be applied to 0-cells of the left operand. The verb has right rank 0, and the right operand has rank 2, so the operation will be applied to 0-cells of the right operand. The left frame is 2, the right frame is 2 3 .
The common frame is 2, with length 1, so think of each operand as a list of 2 _1-cells |
100 | 200
0 1 2 | 3 4 5
The _1-cells of the operands are paired: |
100 | 0 1 2
200 | 3 4 5
The longer-frame operand (the right one) is broken up into operand 0-cells, each being paired with a copy of the shorter-frame operand cell. Each paired _1-cell becomes a row of paired operand cells: |
100 | 0
100 | 1
100 | 2
200 | 3
200 | 4
200 | 5
The operation dyad + is performed on each pair of cells: |
100 | 101 | 102
203 | 204 | 205
Since each result is an atom, and the longer frame is 2 3, the result is an array with shape 2 3 | 100 101 102 203 204 205

Figure 9. Execution of 100 200 + i. 2 3

defverbs 'plus"0'

100 200 plus i. 2 3

+----------+----------+----------+

|100 plus 0|100 plus 1|100 plus 2|

+----------+----------+----------+

|200 plus 3|200 plus 4|200 plus 5|

+----------+----------+----------+

The simplest and most common case of different-length frames is when the shorter frame is of zero length; in other words, when one of the operands has only one cell. In that case, the single cell is replicated to match every cell of the longer operand. An easy way to force an operand to be viewed as a single cell is to make the verb have infinite rank for that operand. This is not a special case—the behavior follows from the rules already given—but it's worth an example:

'abc' ,"_ 0 'defg'

abcd

abce

abcf

abcg

The verb (dyad ,"_ 0) has left rank _, and the left operand has rank 1, so the operation will be applied to 1-cells of the left operand. The verb has right rank 0, and the right operand has rank 1, so the operation will be applied to 0-cells of the right operand. The left frame is (empty), the right frame is 4 .
The common frame is (empty), with length 0, so take each operand in its entirety: |
abc
defg
The cells of the operands are paired: |
abc | defg
The longer-frame operand (the right one) is broken up into operand 0-cells, each being paired with a copy of the shorter-frame operand cell: |
abc | d
abc | e
abc | f
abc | g
The operation dyad , is performed on each pair of cells: |
abcd | abce | abcf | abcg
Since each result is a 1-cell with length 4, and the longer frame is 4, the result is an array with shape 4 4 | abcd abce abcf abcg

Figure 10. Execution of 'abc' ,"_ 0 'defg'

defverbs 'comma'

'abc' comma"_ 0 'defg'

+-----------+-----------+-----------+-----------+

|abc comma d|abc comma e|abc comma f|abc comma g|

+-----------+-----------+-----------+-----------+

You must thoroughly understand this example, where one operand has only one cell, because it occurs frequently. The handling of the general case of dissimilar frames is uncommon enough that you do not need to understand it perfectly right now—you'll know when you need it, and you can sweat out the solution the first few times. Here are a few observations that may help when that time comes:

It is always entire cells of the operand with the shorter frame that are replicated. A cell is never tampered with; nothing inside a cell will be replicated. And, it is not the entire shorter-frame operand that is replicated, but cells singly, to match the surplus frame of the other operand.

This fact, that single operand cells are replicated, is implied by the decision that the shorter frame must be a prefix of the longer frame: the single cell is the only unit that can be replicated, since the surplus frame is at the end of the frame rather than the beginning. Take a moment to see that this was a good design decision. Why should the following fail?

1 2 3 + i. 2 3

|length error

| 1 2 3 +i.2 3

The 'length error' means that the operands do not agree, because the frame-prefix rule is not met. Your first thought might be that adding a 3-item list to an array of 2 3-item lists should be something that a fancy language like J would do without complaining; if so, think more deeply. J does give you a way to add lists together—just tell J to apply the verb to lists:

1 2 3 +"1 i. 2 3

1 3 5

4 6 8

Operands in which one shape is a suffix of the other, as in this example, are handled by making the verb have the rank of the lower-rank operand; that single operand cell will then be paired with all the cells of the other operand. By requiring dissimilar frames to match at the beginning, J gives you more control over implicit looping, because each different verb-rank causes different operand cells to be paired. If dissimilar frames matched at the end, the pairing of operand cells would be the same regardless of verb-rank.

Order of Execution in Implied Loops

Whenever a verb is applied to an operand whose rank is higher than the verb's rank, an implied loop is created, as we have discussed above. The order in which the verb is applied to the cells is undefined. The order used on one machine may not be that used on another one, and the ordering may not be predictable at all. If your verb has side effects, you must insure that they do not depend on the order of execution.

Current versions of the interpreter apply the verb to cells in order, but that may change in future releases.

A Mistake To Avoid

Do not fall into the error of thinking that v"r is 'v with the rank changed to r'. It is not. Nothing can ever change the rank of the verb v—v"r is a new verb which has the rank r. This distinction will become important presently as we discuss nested loops. Consider the verb v"1"2,which is parsed as (v"1)"2 . If v"r changed the rank of v, it would follow that v"1"2 would be 'v with the rank changed to 1 and then to 2', i. e. identical to v"2 . But it is not: actually, v"1"2 applies v"1 on the 2-cells of the operand, while v"2 applies v on those same cells—and we have seen that v and v"1 are very different verbs:

+/"1"2 i. 2 3 4

6 22 38

54 70 86

+/"2 i. 2 3 4

12 15 18 21

48 51 54 57

Summing the 2-cells (+/"2) is not the same as summing the 1-cells within each 2-cell (+/"1"2). Make sure you see why.

Ah, you may say, but +/"1"2 is equivalent to +/"1 . You are right for the monadic case, but not for the dyadic:

(i. 3 4) +"1"2 i. 2 3 4

0 2 4 6

8 10 12 14

16 18 20 22

12 14 16 18

20 22 24 26

28 30 32 34

(i. 3 4) +"1 i. 2 3 4

|length error

| (i.3 4) +"1 i.2 3 4

Dyad +"1"2 is executed as (+"1)"2, i. e. it has rank 2. So, there is only one 2-cell of the left operand i. 3 4, and that cell is replicated to match the shape of the right operand. The operands then agree, and the 1-cells can be added. Trying to add the 1-cells directly with +"1 fails, because the frames of the operands with respect to 1-cells do not agree.

The situation becomes even more complicated if the assigned left and right ranks are not the same. My advice to you is simple: remember that u"r is a new verb that executes u on r-cells.

<< >> Contents Help