Help / JforC / Compound Verbs

From J Wiki
< Help(Redirected from Help/JforC/Compound Verbs)
Jump to navigation Jump to search


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


                                                                                                             12. Compound Verbs

On New Year's Day my rich uncle gives me x dollars, which I add to the y dollars I already have earning 4% interest.  How much money will I have at the end of the year?  Simple in J--I just write 1.04 * x + y, and I have the answer, whether x and y are scalars or arrays.  That's nice, but I have an enviable problem: I expect his largesse to continue, and in my anticipation I have estimated his gifts for the next few years as the list x; I want to know what I'll be left with at the end.  I need to pass each year's starting balance into the calculation for the next year.  I know what I want the result to look like: it'll be v/ (|.x) , y which will be evaluated as xn v ...x2 v x1 v x0 v y .  But what is v?  The problem with 1.04 * x + y is that it contains 2 verbs and a constant, and I need it all lumped into a single verb so that I can have the adverb dyad / modify the whole thing.  One solution would be to create the verb

v =: dyad : '1.04 * x + y'

after which v/ (|.x),y works, but it's a shame to have to interrupt a J sentence just to define a verb with such a puny function--I want magic words to let me say (1.04 * + abracadabra...combine!)/ (|.x),y .  J has such magic words, and we will learn a few now.

The magic words will join verbs and nouns together, so they must be modifiers: adverbs and conjunctions.  Before we start, we need a little notation to help with the different cases we will encounter.  Given a conjunction c or adverb a, we call its left operand m if it is a noun, or u if it is a verb.  Similarly we call a conjunction's right operand n if it is  noun, v if a verb.  There are four possible ways to invoke a conjunction (u c v, m c v, u c n, and m c n) and two for an adverb (u a and m a) and they are defined independently.  Moreover, the derived verb produced by the invocation (the derived entity may be a noun, adverb, or conjunction too but that is unusual) can be used as a dyad (e. g. x u c n y) or as a monad (e. g. m a y), and those cases are defined independently as well.  You won't get the cases mixed up, because verbs and nouns are so different that it will seem natural for u c n to be different from u c v; just be aware that the variants are many and that we will be learning a tiny subset of J's toolkit.  The adverb / is an example: we have learned about monad u/, but dyad u/ is very different, as is m/ .

Verb Sequences--u@:v and u@v

u@:v creates a derived verb of infinite rank that applies v to its argument(s) and then applies u to the result.  In other words, u@:v y is the same as u v y and x u@:v y is the same as u x v y .  Examples:

   {. @: /: 3 1 4 1 5 9
1

Monad /: produced the permutation 1 3 0 2 4 5 of which we took the first item.

   1 2 3 +/@:* 1 2 3
14

Dyad * produced 1 4 9 whose items we then summed.  fndisplay shows the details:

   defverbs 'plus"0 times"0'
   1 2 3 plus/@:times 1 2 3
+-------------------------------------------+
|(1 times 1) plus (2 times 2) plus 3 times 3|
+-------------------------------------------+

u@v is like u@:v except that the rank of the derived verb is the rank of v (also expressible as (u@:v)"v because u"v is defined to have the function of u with the rank of v).  My advice is to stick to @: and avoid @ unless you're sure you need it.

The Difference Between u@:v and u@v

Because u@:v and u@v have very similar definitions, and produce identical results in many cases, almost every beginning J programmer confounds the two.  The key is to remember that each sequence produces a new verb which has a rank.  In u@:v, this rank is infinite, so that in x u@:v y, the derived verb u@:v is applied to the entire x and y, meaning that v is applied to the entire x and y and u is applied to the entire result of .  In the other case, the rank of u@v is the rank of v, so in x u@v y the verb u@v is applied to individual cells of x and y, where the cell-size is given by the rank of : for each of those cells, v is applied followed by u, and the results from the cells are collected into an array.

If we try to take the sum-of-products using u@v instead of u@:v, we see the difference between the two forms:

   1 2 3 +/@* 1 2 3
1 4 9

What happened? We thought we were multiplying the vectors and then taking the sum.  Because we used @ rather than @:, the derived verb had the rank of dyad *, namely 0, which means that the derived verb was applied to each cell: at each cell we multiplied and then took the sum of the single cell.  In fndisplay form,

   defverbs 'plus"0 times"0'
   1 2 3 plus/@times 1 2 3
+---------+---------+---------+
|1 times 1|2 times 2|3 times 3|
+---------+---------+---------+

plus never got executed, because plus/ was applied to 1-element lists, leaving in each case the single element.

Many J programmers think of @ and @: as establishing a different kind of connection between u and v, with u@:v applying u to the entire result of v and u@v applying u to result cells of v (where a result cell is the output produced by applying v to a single operand cell).  Such an interpretation makes it easy to understand the operation of +/@* : +/ is applied on result cells of *, which are scalars.

The connection interpretation of u@v correctly accounts for the results produced by J, but as you use it you should be aware that it is inaccurate because it suggests that v is executed against the operand(s) in their entirety.  The actual cell-at-a-time execution of u@v is different in two ways: it is slower because the verb v must be restarted for each cell; and if the temporary space required by u or v is large, cell-at-a-time execution uses less space because the temporary space for each cell is freed before the next cell is processed.

An Exercise in @ and @:

Given the definition

   ]a =: 1 2 3 ; 4 5 ; 6 7 8
+-----+---+-----+
|1 2 3|4 5|6 7 8|
+-----+---+-----+

we want to create a verb that will throw away the first box (using the Behead verb, monad }.), and for each remaining box open it and keep just the first atom (using the Head verb, monad {.).  The result will be a list of the first items in all boxes except the first, in this case 4 6 .  Understand how the following verbs work or don't work:

   }.@:({.@>) a    NB. Verb 1
4 6
   }.@({.@>) a   NB. Verb 2 (the output is 3 blank lines)
 
 
   }.@:{.@> a NB. Verb 3 (the output is 3 blank lines)
 
 
   }.@({.@:>) a   NB. Verb 4
2 3
   {.@>@}. a   NB. Verb 5
4 6
   {.@:>@}. a   NB. Verb 6
4 5 0

Solutions:

In verb 1 (}.@:({.@>)), the overall verb has infinite rank because of the use of @:, so the entire a is applied to ({.@>) and then }. operates on the entire result.  {.@> has rank 0 because @ was used and the rank of > is 0.  So each atom of a, i. e. each box, is separately opened and the first element taken, giving a 3-element vector 1 4 6 which is then beheaded to give 4 6 .  The following table shows how the operand is passed through the verb, with each row representing a cell of input to each verb.  The table should be read from right to left.

Result
of }.
(final
result)

Processing of {.@>

Cells of }.@:({.@>)

Result of
{.@>

Result
of {.

Result
of >

Cells of
{.@>

4 6

1 4 6

1

1 2 3

1 2 3

1 2 3

4 5

6 7 8

4

4 5

4 5

6

6 7 8

6 7 8

In verb 2 (}.@({.@>)), the overall verb has rank 0 because @ was used and the rank of ({.@>) is 0.  So each atom of a, i. e. each box, will be opened, the first element taken, and then the first element of that beheaded, leaving an empty list; this is done for each atom of a, giving a result of 3 0$0 which displays 3 blank lines.

Final
result

Result
of }.

Processing of {.@>

Cells of }.@({.@>)

Result of
{.@>

Result
of {.

Result
of >

Cells of
{.@>

3$0

0$0

1

1

1 2 3

1 2 3

1 2 3

0$0

4

4

4 5

4 5

4 5

0$0

6

6

6 7 8

6 7 8

6 7 8

In verb 3 (}.@:{.@>), the ordering rules for modifiers cause the verb to be equivalent to (}.@:{.)@>, which again has rank 0 because of @ with right operand of >, so each atom is passed through the whole verb giving the same result as verb 2.

Final
result

Processing of }.@:{.

Result of >

Cells of }.@:{.@>

Result of
}.@:{.

Result
of }.

Result
of {.

3$0

0$0

0$0

1

1 2 3

1 2 3

0$0

0$0

4

4 5

4 5

0$0

0$0

6

6 7 8

6 7 8

In verb 4 (}.@({.@:>)), the rank of the overall verb is infinite because @: makes the rank of ({.@:>) infinite.  The entire a is passed into ({.@:>), where it is opened and padded with fills to make an array of shape 3 3; then the first item is taken, yielding 1 2 3; then this list is beheaded, giving 2 3 .

Result
of }.
(final
result)

Processing of {.@:>

Cells of }.@({.@:>)

Result of
{.@:>

Result
of {.

Result
of >

Cells of
{.@:>

2 3

1 2 3

1 2 3

1 2 3
4 5 0
6 7 8

1 2 3

4 5

6 7 8

1 2 3

4 5

6 7 8

Verb 5 ({.@>@}.) is equivalent to ({.@>)@}. which has infinite rank because }. does.  The list of boxes, taken in full, is beheaded and the result (4 5;6 7 8) passed to ({.@>).  This verb has rank 0 (from >), so each box is individually opened and the first item taken, giving the result 4 6 .

Final
result

Processing of {.@>

Cells of {.@>@}.

Result of
{.@>

Result
of {.

Result
of >

Cells
of {.@>

4 6

4

4

4 5

4 5

1 2 3

4 5

6 7 8

6

6

6 7 8

6 7 8

Verb 6 ({.@:>@}.) is equivalent to ({.@:>)@}. which is like verb 5 except that ({.@:>) has infinite rank.  When the beheaded list of boxes (4 5;6 7 8) is presented to this verb, the whole list is processed at once: it is opened and filled with zeros to produce an array of shape 2 3, then the first item is taken giving 4 5 0 (the 0 was added as a fill when the list was opened).

Final
result

Processing of {.@:>

Cells of {.@:>@}.

Result of
{.@:>

Result
of {.

Result
of >

Cells
of {.@:>

4 5 0

4 5 0

4 5 0

4 5 0
6 7 8

4 5

6 7 8

1 2 3

4 5

6 7 8

Making a Monad Into a Dyad: The Verbs [ and ]

The characters [ and ] are not paired in J; each is an independent verb.  This is jarring at first but you'll get used to it.

[ and ] are identity verbs: they have infinite rank, and ] y and [ y both result in .  As dyads, they pick one operand: x [ y is x, and x ] y is .  They can be useful when you have a monadic verb that for one reason or another must be used as a dyad with an unwanted operand: then x v@:[ y applies v to x, and x v@:] y applies v to .  Example:

   1 2 3 {.@:[ 4 5 6
1

Here are some other uses of [ and ] .  We have already met the first one, which is to display the result of an assignment:

   a =. 1 2 3

produces no typeout, but

   ]a =. 1 2 3
1 2 3

Second, [ can be used to put multiple assignments on the same line, since each application of [ ignores what is to its right:

   a =. 5 [ b =. 'abc' [ c =. 0

Finally, ] can be used instead of parentheses to separate numbers that would otherwise be treated as a list:

   5 ,"0 1 2
|syntax error
|   5    ,"0 1 2
   5 ,"0 (1 2)
5 1
5 2
   5 ,"0 ] 1 2
5 1
5 2

Making a Dyad Into a Monad: u&n and m&v

A dyadic verb takes two operands, but if you know you are going to hold one fixed, you can create a monadic verb out of the combination of the dyad and the fixed operand; the monad's operand will be applied to whichever operand of the dyad was not held fixed.  The conjunction &, when one of its operands is a noun, fixes an operand to a dyad: m&v y has infinite rank and is equivalent to m v y; u&n y has infinite rank and is equivalent to y u n .  Examples:

   2&^ 0 1 2 3
1 2 4 8
   (-&2) 4 5 6
2 3 4

Note that the compound u&n or m&v actually does have a meaning when used dyadically (it means apply the monad form x times), so if you are making a monad from a dyad, be sure to use the resulting verb monadically.

Now we can solve our original problem, which was to put 1.04 * x + y into the form x v y v should be

1.04&* @: +

which we can verify using fndisplay as

   defverbs 'plus"0 times"0'
   defnouns 'x y'
   x  1.04&times @: plus  y
+-------------------+
|1.04 times x plus y|
+-------------------+

and to get my total savings after receiving all payments I use monad / to apply that verb for each payment, giving the expression:

1.04&* @: + / (|.x) , y

Let's take a moment to understand the parsing and execution of 1.04&* @: + / The left operand of a modifier includes all preceding words up to and including the nearest noun or verb that is not immediately preceded by a conjunction.  This is a precise way of saying that modifiers associate left to right.  In the phrase 1.04&* @: + /, the 1.04 is not preceded by a conjunction, so it is the beginning of all the conjunctions' left operands, and the verb is parsed as if it were written (((1.04&*) @: +) / ) .

Note that left-to-right association of modifiers corresponds to right-to-left execution.  When the derived verb monad (((1.04&*) @: +) / ) is executed, it performs according to the definition of monad u/, with ((1.04&*) @: +) as the u; execution of monad u/ inserts u between items, so ((1.04&*) @: +) is executed between items; at each such execution the dyad + is executed first, followed by the monad 1.04&* .  Fortunately, the result of the parsing rules is that conjunctions and adverbs, just like verbs, should be read right-to-left.

If I wanted to spend half of my uncle's money , the amount I would be left with is

0.5 * 1.04&* @: + / (|.x) , y

No parentheses are needed, because 1.04 is still not preceded by a conjunction and so 1.04&* @: + / (|.x),y is still evaluated before that value is multiplied by 0.5 .

The Invisible Modifiers: Hooks and Forks

A modifier, as we have seen, can take verbs as operands to produce a new verb that combines the functions of the operand verbs in a particular way.  J has two special modifiers that don't even use a symbol to specify the combination: they are recognized by syntax.  These modifiers are the fork

   (verb0 verb1 verb2)

and the hook

   (verb0 verb1)

Without some special definition, such a sequence of verbs with no noun operand would be undefined.

When given an operand y and an optional operand x, the fork creates a verb of infinite rank that executes as

   x (f g h) y   is   (x f y) g (x h y)

For example:

   (+/ % #) 1 2 3
2
   (+/ 1 2 3) % (# 1 2 3)
2

The fork was executed as shown in the second line, with the same result.  This hook found the average value of the list operand.

   8 (<. % >.) 10
0.8
   (8 <. 10) % (8 >. 10)
0.8

The fork calculated the smaller operand as a fraction of the larger.

The hook takes an operand y and an optional operand x to produce

   (f g) y   is   y f g y
   x (f g) y   is   x f g y
   (% +/) 0 4 1 5
0 0.4 0.1 0.5
   0 4 1 5 % +/ 0 4 1 5
0 0.4 0.1 0.5

The list was divided by its sum, producing a result whose items sum to 1.

You can use the fork and hook to save some repetition of operands, as in the examples above.  In the second part of this book, we will see that the hook and fork are gateways to the language of tacit programming, which allows you to describe programs entirely as a sequence of functions without visibly referring to operands.

 

 

Once you get the hang of it, you will be able to understand and build composite verbs of great power.  That will be a useful skill to develop, because you never know when you are going to want to make some sequence of functions the operand of a modifier, and then you're going to have to be able to express the sequence in a single compound verb.  It will take a lot of practice, as well as coding techniques to break a gristly mass of conjunction-bound words into digestible pieces (we'll learn them later).  For the time being, be content if you can understand the simple examples shown above.


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