>>  <<  Usr  Pri  JfC  LJ  Phr  Dic  Voc  !:  Help  J for C Programmers

                                                      6.     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 7 scattered chapters, showing how to replace different variants of loops:

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

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

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

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

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

Loops that accumulate information between iterations of the loop;

Loops that implement finite-state machines.

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 the rank of the verb and the rank of the operand.  For the important case of a verb infinite rank, according to this rule r will be the rank of the operand, which is another way of saying that the verb applies to the operand in its entirety.

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

Think of the operand as an array with shape f made up of 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 2x2 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 2x2 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 2x2 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 2x2 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').  Monad +/ has infinite rank: its rank is _ (infinity), which means, if you work through the description above, that it always works on its entire operand as a single cell.  Its action is to take the total of the items, which are the _1-cells of the operand:

   +/ 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 in detail.

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 rather than rank _--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 " .  (Remember that in J, " is not a paired quote character, but just a single primitive with a defined function).

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 2x3 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 2x3 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

Fills

If you worked through that last example i."0 (2 2 2), 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 interpreter 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

The operand has rank 1, so the _1-cells are atoms.  Applying monad SumItems on each one has no effect.  The sentence is equivalent to +/ "0 i. 2 3 .

   +/ "_1 i. 2 3

3 12

The operand has rank 2, so this expression totals the items in each 1-cell.  The sentence is equivalent to +/ "1 i. 2 3 .

   +/ "_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 2x2 array of totals.  The sentence is equivalent to +/ "1 i. 2 2 3 .

Rank Makes Verbs Automatically Extensible

We have been focusing so closely on shapes, cells, and frames that we haven't paid attention to one of the great benefits of assigning a rank to a verb: extensibility.  When you give a verb the rank r, you only have to write the verb to perform correctly on cells of rank ≤r.  Your verb will also automatically work on operands with rank >r: J will apply the verb on all the r-cells and collect the results using the frame.

If you write a verb to find the length of a vector, and give that verb the rank 1:

   len =: verb : '%: +/ *: y' "1  NB. %: sqrt  *: square

you can use it to take the length of a single vector:

   len 3 4 5

7.07107

or two vectors:

   len i. 4 3

2.23607 7.07107 12.2066 17.3781

or a 2x4 array of vectors:

   len i. 2 4 3

2.23607 7.07107 12.2066 17.3781

 22.561 27.7489 32.9393 38.1314

All is grist that comes to this mill!

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 verb ranks interact with each other as well as with the ranks of the operands themselves, 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 2x2 array of 0-cells, and the right operand as a 2x2 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 .

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 is not easy to follow, so you might want to skim over it and read it in detail after you have studied the subsequent examples.

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.  Processing starts by working on cells of each operand that have cf as the frame (we will call these the (-rcf)-cells, with the understanding that if rcf is 0, the (-rcf)-cells are the operands in their entirety).  Considering the (-rcf)-cells, we see that for the operand with the shorter frame, these cells have exactly the rank that the verb operates 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 (always possible, 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

When you have operands in which one shape is a suffix of the other, as in this example, you handle them 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.

Nested Loops

As we have seen, looping is implicit whenever a verb is applied.  If the frames are different, cells of the shorter-frame operand are replicated to match the cells of the longer-frame operand; that's one loop, or more depending on the difference in the lengths of frame.  Then, the verb is applied to each pair of cells, which is another set of loops.

When you create a verb v"r with ranks r, you are controlling the looping.  You are interposing the rank r into the operation, causing the loops to operate with respect to the r-frame and the r-cells of the operand.  But the ranks of the original verb v are still applicable: they come into play when 'the verb is applied to each pair of cells'.  Each time the verb v is applied to a pair of cells, another suite of loops is applied, this time using the ranks of v.  If the ranks of v are not less than r, in other words if the r-cells are individual cells of the verb v, this second suite of loops will have no effect; but if the ranks of v are less than r, they will come into play.  You can use nested ranks to produce the effect of nested loops in C.

Try to figure out what this sentence will produce:

100 200 300 +"0"0 _ (1 2 3 4)

The verb is executed as (+"0)"0 _ .  This is of the form v"r where v is +"0 and r is 0 _ .  So each 0-cell of 100 200 300 is paired with the entire 1 2 3 4, and +"0 is executed between each pair.  The first pair is 100 paired with 1 2 3 4, and +"0 causes the 100 to be replicated, after which the scalars are added, producing 101 102 103 104 .   The other parts of the operands are processed equivalently, so the result is

   100 200 300 +"0"0 _ (1 2 3 4)

101 102 103 104

201 202 203 204

301 302 303 304

Because the rank of + is 0 0, + and +"0 are equivalent, and I could have written +"0 _ with the same result.  Ignore that--it's just accidental that the verb that's easiest to understand has rank 0.  The important point is that you can create nested loops using nested ranks.  Understand the following results, and you will be well on your way to taking the Loopless Pledge:

   100 200 +"0"_ 0 (1 2 3)  NB. See the difference!

101 201

102 202

103 203

 

'ab' ,"0"0 _ 'def'

ad

ae

af

 

bd

be

bf

   'ab' ,"0"_ 0 'def'

ad

bd

 

ae

be

 

af

bf

 

A Mistake To Avoid

As the preceding section shows, you must 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 that has the rank r.  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.


>>  <<  Usr  Pri  JfC  LJ  Phr  Dic  Voc  !:  Help  J for C Programmers