Help / JforC / More Verbs For Boxes

From J Wiki
Jump to navigation Jump to search


>> << Pri JfC LJ Phr Dic Voc !: Rel NuVoc wd Help J for C Programmers


                                                                                              17. More Verbs For Boxes

Box and Join: Dyad ; (Link)

Dyad ; has infinite rank.  It boxes its operands and concatenates the boxes:

   'abc' ; 1 2 3
+---+-----+
|abc|1 2 3|
+---+-----+

Dyad ; is the easiest way to create a list of boxes:

   'abc' ; 1 2 3 ; (i. 2 2)
+---+-----+---+
|abc|1 2 3|0 1|
|   |     |2 3|
+---+-----+---+

Did you notice that I gave you an inaccurate definition?  If dyad ; just boxed the operands and concatenated, its result would be like

   dyadsemicolon =: dyad : '(<x) , (<y)'
   'abc' dyadsemicolon 1 2 3 dyadsemicolon (i. 2 2)
+---+-----------+
|abc|+-----+---+|
|   ||1 2 3|0 1||
|   ||     |2 3||
|   |+-----+---+|
+---+-----------+

That's not the list of boxes we wanted!  Actually dyad ; is more subtle: x ; y always boxes x, but it boxes y only if y is unboxed.  That produces the behavior we want most of the time; the exception is when the last item in the list is boxed already:

   (<'abc');(<'def');(<'ghi')
+-----+-----+---+
|+---+|+---+|ghi|
||abc|||def||   |
|+---+|+---+|   |
+-----+-----+---+

If we expected all the items to be boxed the same, we are disappointed.  We must develop the habit that when we use a sequence of dyad ;s we box the last operand (unless we are sure it is unboxed, and even then we might do it to reinforce our habit):

   (<'abc');(<'def');<(<'ghi')
+-----+-----+-----+
|+---+|+---+|+---+|
||abc|||def|||ghi||
|+---+|+---+|+---+|
+-----+-----+-----+

Unbox and Join: Monad ; (Raze)

Monad ; (infinite rank) removes one level of boxing from an array of boxes, concatenating the contents of all the boxes into one long array. 

The shape of the operand of ; is immaterial.  ; looks inside the boxes of its operand, and works with the contents of each box.  If the contents do not have the same rank, contents of lower rank are brought up to the rank of the highest-rank contents, by treating the lower-rank contents as the sole item of a higher-rank array.  For example, if one box contains i. 2 3 (rank 2) and another contains 1 2 (rank 1), the second contents will re converted to 1 2 $ 1 2, which is a rank-2 array whose only item is 1 2.  If some other box contained i. 2 3 4 (rank 3), the 1 2 would be converted to 1 1 2 $ 1 2.

Once the contents have been brought up to common rank, the items of the contents are examined.  If these items do not have a common shape--the items of the contents of the operand boxes, mind you, not the contents themselves--they are brought up to a common shape as described below.  Examples:

   'abc';'d';'ef'
+---+-+--+
|abc|d|ef|
+---+-+--+

A list of boxes.

   ; 'abc';'d';'ef'
abcdef

The items, which are scalars, are joined into one long list.

   ; 1 ; 2 3 4
1 2 3 4

It works for numbers too.

   ; (i. 2 3) ; (i. 2 3)

0 1 2

3 4 5

0 1 2

3 4 5

The items are lists, so the lists are concatenated into a rank-2 array.

   ; 1 2 ; i. 2 3

1 2 0

0 1 2

3 4 5

Here the second box contains a rank-2 array, while the first box contains a rank-1 array.  The items of highest rank have rank 1, which means that rank-1 items are going to be lined up as the items of a rank-2 result.  Any contents of lower rank are brought up to the rank of the highest-rank contents, by adding single-length leading axes; after that operation, the items of all the modified contents have the same rank (but not necessarily the same shape).  If the shapes of any of those items differ, verb fills are added to bring each axis up to the length of the longest axis; then the items are assembled into a list whose rank is 1 higher than the rank of an item.  In this example the concatenated items have rank 1, and verb fill was added to bring the single item of the first box up to a length of 3.

The Atomic Exception

   ; 1 ; ,: 2 3 4
1 1 1
2 3 4

There is one amendment to the processing as described above: if any of the contents is an atom, it is replicated, rather than padded with fills, to bring it up to the shape of an item of the result before items are concatenated.  Here the atom 1 was replicated to become 1 1 1 .

   ; (,1) ; ,: 2 3 4
1 0 0
2 3 4

Here the first box was a 1-item list rather than an atom, so it was padded with fills rather than replicated.

When you have an array of boxes, the difference between opening it with monad > and with monad ; is that monad > keeps the frame of the array of boxes, and brings every opened box up to the same shape, while monad ; just runs the items of the contents together into one long list with no regard for the shape of the array of boxes.

Dyad , Revisited--the Case of Dissimilar Items

When we discussed dyad , we glossed over the treatment of operands with items of different shapes.  Now we can reveal that the padding and replication for dyad , is just like what monad ; does on the contents of boxes.  In fact, x , y is equivalent to ; (<x),(<y) .

Verbs With Many Operands--Multiple Assignment

Dyad ; is part of the standard J method of passing many operands to a verb.  The invocation of the verb normally looks like this:

   verbname op1 ; op2 ... ;< opn

(the < is needed only if opn is boxed), and the verb that is so invoked looks like:

verbname =: monad define
'op1 op2 ... opn' =. y
remainder of verb
)

The line 'op1 op2 ... opn' =. y is J's handy multiple assignment.  When the target of the assignment is a string, the string is broken into words and the words are matched with items of the value being assigned (they must match one-for-one or a length error results).  Then, each word from the string is used as a name which is assigned the corresponding item of the value.  If the value being assigned is boxed, each item is unboxed before it is assigned.

When defined and invoked as shown above, the variables op1, op2, etc. during execution of the called verb will hold the values of op1, op2, etc. in the calling verb.

Multiple assignment is not restricted to parameter-passing; you may use it as you see fit, if only to save typing.  I have found it very useful in loading configuration parameters from a file: the file contains both noun-names and the values, with the values being assigned to the names by multiple assignment.  Such a design is easily portable from release to release of a product, since the file has no 'format'--it simply defines all the names it understands.

A multiple assignment can produce verbs and modifiers as well as nouns.  You put a '`' character before your list of names:

   '`name1 name2...' =. list of atomic representations

and each name is assigned the entity described by the atomic representation.  Each atomic representation is a noun, but it may describe any kind of entity.  Usually your entities will be verbs, and then they can be converted to atomic representations by `, as in

   '`add subtract mult div' =. +`-`*`%

Instead of a string as the left operand, you can have a list of boxes.  This is convenient if the list of names has to be computed.  If nlist has the list of names,

   (nlist) =. list of values

will assign the values to the names.

Dyad { Revisited: The Full Story

Now that we know about boxes, we can understand the full description of the selection verb dyad .  In the general form, the left argument of x { y is a box whose contents is a list of boxes.  Pictorially, it is

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

|+-----------------+-----------------+---+|

||axis-0 selections|axis-1 selections|...||

|+-----------------+-----------------+---+|

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

We will call the inner boxes (i. e. the items of the contents of the box x) the selectors.  The first selector gives the indexes to be selected along the first axis (i. e. axis 0); the second selector gives the selections for axis 1; and so on.

   i. 2 2 3
0  1  2
3  4  5
 
6  7  8
9 10 11
   <0;1;1
+-------+
|+-+-+-+|
||0|1|1||
|+-+-+-+|
+-------+
   (<0;1;1) { i. 2 2 3
4

If not all axes are specified, the selectors are applied starting with the leading axis and any axes left over at the end are taken in full:

   (<0;1) { i. 2 2 3
3 4 5

Each of the selectors may contain either a scalar or a list.  If a selector contains a scalar, the corresponding axis will disappear from the shape of the result, as in the examples above.  If a selector contains a list, even a list with only one item, the corresponding axis will remain in the shape of the result (its length will be the length of the selection list):

   (<0;1 0) { i. 2 2 3
3 4 5
0 1 2

We select two rows of item number 0.  The rows stay in the order we requested them, and the result has rank 2.

   (<0 1;1 0;2) { i. 2 2 3
 5 2
11 8

Understand where each number came from.  We are taking a 2x2 array of 1-cells, but only item 2 from each 1-cell.  That leaves a 2x2 result.

   (<0;,1) { i. 2 2 3
3 4 5
   $ (<0;1) { i. 2 2 3
3
   $ (<0;,1) { i. 2 2 3
1 3

In the last example we are requesting a list of 1-cells; even though the list has only one item, its axis remains in the result.

If a selector contains a box (rather than the usual numeric), it calls for complementary selection along that axis: the contents of that box (i. e. the contents of the contents of the selector) indicate the indexes to exclude from the selection, and all other indexes are selected.  Such a selector is considered to be specifying the list of non-excluded indexes, so the corresponding axis remains in the result.  Example:

   (<0;1;<<1)
+---------+
|+-+-+---+|
||0|1|+-+||
|| | ||1|||
|| | |+-+||
|+-+-+---+|
+---------+
   (<0;1;<<1) { i. 2 2 3
3 5

We select a single 2-cell, and from that a single 1-cell, and within that we select all except item 1.  The result is a 2-item list.  Note that we had to put an extra < after the last ; to ensure that the contents of the last selector was boxed.

   (<0;(<0$0);2)
+--------+
|+-+--+-+|
||0|++|2||
|| |||| ||
|| |++| ||
|+-+--+-+|
+--------+
   (<0;(<0$0);2) { i. 2 2 3
2 5

Complementary indexing can be used to select all of an axis, as in this example.  We request all of axis 1 except the named items, and then we name an empty list: we get all the items.  This trick is called for when we need to specify a selector for some axis after the axis we want to take completely (trailing axes can be taken in full simply by omitting their selectors).  Since a: is equivalent to <0$0, the thrifty J idiom for the x operand above would be (<0;a:;2) .  You may think of a: as 'all' in this context.

Simplification 1: Remove Inner Boxing If Selectors Are Scalars

If our use of x { y does not require any selector to specify a list (i. e. each selector is a scalar), we are allowed to omit the boxing of the selectors.  This leaves x as a boxed numeric list (or scalar) in which the successive items indicate the single index to be selected from each axis.  This form, in which x is <i,j,k..., corresponds to C's array[i][j][k]... .

   <0 1
+---+
|0 1|
+---+
   <0;1
+-----+
|+-+-+|
||0|1||
|+-+-+|
+-----+
   (<0 1) { i. 2 2 3
3 4 5
   (<0;1) { i. 2 2 3
3 4 5

The results are identical.

Simplification 2: Remove All Boxing To Select Full Items

As a final simplification, if the selection is just a single item from axis 0, the left operand of dyad { may be left unboxed.  This is the form in which we first met dyad .  Now that you have learned dyad { completely, take this quiz: what is the difference between 0 1 { y and (<<0 1) { y?

   0 1 { i. 6
0 1
   (<<0 1) { i. 6
0 1

Answer: the results are identical, but because the left rank of dyad { is 0, 0 1 { y applies dyad { twice, once for each atom of 0 1, and collects the results into an array, while (<<0 1) { y applies dyad { just once.  The code for dyad { handles unboxed x efficiently, so which form you use is a matter of convenience.

Split String Into J Words: Monad ;:

Monad ;: splits a string of characters into J words, putting each word into a box of its own.  Each word is a list of characters, so the result of monad ;: is a list of boxed lists:

   ;: 'Words, words; and more words.'
+-----+-+-----+-+---+----+------+
|Words|,|words|;|and|more|words.|
+-----+-+-----+-+---+----+------+

Monad ;: is a handy way to get boxed character strings, or to break an input stream into words if your language has word-formation rules similar to J's.  Be aware that if the operand has an unmatched quote, monad ;: will fail.

Fetch From Structure: Dyad {::

Dyad {:: (this is a single primitive) has left rank 1, right rank infinite.  It selects an atom from an array of boxes and opens it; it is therefore analogous to the . (class member) operator in C:

   1 {:: 'abc';1 2 3; i. 2 2
1 2 3

Item 1 was selected and opened.

x {:: y can go through multiple levels of structure referencing at once.  If x is a list of boxes, the first box must be a valid left argument to dyad {; it is used to select an item of y, which is then opened; the next box of x selects an item from that opened box of y, which item is then opened; and so on till x is exhausted:

   struct1 =: 'abc' ; 1 2 3
   ]struct2 =: 'def';struct1;4 5 6
+---+-----------+-----+
|def|+---+-----+|4 5 6|
|   ||abc|1 2 3||     |
|   |+---+-----+|     |
+---+-----------+-----+

Here we have a structure in which item 1 is another structure.

   1 {:: struct2
+---+-----+
|abc|1 2 3|
+---+-----+

We select and open item 1, resulting in the enclosed structure.

   (1;1) {:: struct2
1 2 3

We select and open item 1 of struct2, and then open item 1 of the enclosed structure.

   (1;<<1 0) {:: struct2
+-----+---+
|1 2 3|abc|
+-----+---+

If the last selection specifies a list of items, as in this example, the selected boxes are not opened.  Note that the Dictionary's description of dyad {:: incorrectly indicates that the boxes are opened.

   (1;<<,1) {:: struct2
+-----+
|1 2 3|
+-----+

Even if the list contains only one item, it is not opened.

Only the last box of x may specify selection of a list of boxes:

   ]a =. <"0 i. 3 3
+-+-+-+
|0|1|2|
+-+-+-+
|3|4|5|
+-+-+-+
|6|7|8|
+-+-+-+

a is a 3x3 array of boxes.

   (1;1) {:: a
|rank error
|   (1;1)    {::a

The first selection took item 1 of .  This was a 3-item list of boxes, and it is inadmissible to open the list and perform further selections.

Note that if x is unboxed, dyad {:: first boxes it and then uses it for selection.  The Dictionary's description does not mention the boxing step.

Note also that 0 {:: y is not allowed if y is a scalar.  > 0 { y may be used instead.

Report Boxing Level: Monad L.

Monad L. has infinite rank and tells you the boxing level of its operand.  Boxing level is defined recursively: if y is unboxed or empty, its boxing level is 0; otherwise its boxing level is one greater than the maximum of the boxing levels of its opened items:

   1 ;< 2 ;< 3 ;< 4
+-+---------+
|1|+-+-----+|
| ||2|+-+-+||
| || ||3|4|||
| || |+-+-+||
| |+-+-----+|
+-+---------+
   L. 1 ;< 2 ;< 3 ;< 4
3
   L. {. 1 ;< 2 ;< 3 ;< 4
1

You can use monad L. to decide how many levels of boxing to remove:

   >^:L. <<<6
6

Note that an empty list of boxes shows boxing level of 0, but the type revealed by 3!:0 is 'boxed'.  Also, fill elements for an empty list of boxes are the boxed fill element a: :

   L. 0$a:
0
   3!:0 (0$a:)
32
   3 {. 0$a:
++++
||||
++++

>> << Pri JfC LJ Phr Dic Voc !: Rel NuVoc wd Help J for C Programmers