<< >> Contents Help

# 38. Forks, Hooks, and Compound Adverbs

Now that you understand execution, and in particular how anonymous entities are created and then executed, you are ready to see forks used in some practical applications. This will be a relief after the last two chapters of theory.

You have learned the rule for the trident called the monadic fork:

(V0 V1 V2) Ny is (V0 Ny) V1 (V2 Ny)

Now learn the other 3 bidents/tridents involving only verbs. The dyadic fork:

Nx (V0 V1 V2) Ny is (Nx V0 Ny) V1 (Nx V2 Ny)

The monadic hook:

(V0 V1) Ny is Ny V0 (V1 Ny)

The dyadic hook:

Nx (V0 V1) Ny is Nx V0 (V1 Ny)

The purpose of taking you through the 2 preceding chapters was for you to understand that 'is' in these definitions is shorthand for 'replaces the parenthesized part with an anonymous entity that when executed on an x and y produces the same result as' (you don't have to be a politician to hesitate over the meaning of 'is').

It may be helpful to think of these bidents and tridents as ghostly conjunctions, with no actual symbol, that create an entity (the bident/trident) out of the sequence of verbs. The entity so created is quite real: it is executed just like any anonymous verb created by a modifier.

You can see that for both the hooks and the forks, the monadic case is derived from the dyadic: for forks by omitting Nx, and for hooks by replacing Nx with Ny . The verbs produced by hooks and forks have infinite rank.

Using hooks and forks, assisted by all the modifiers we have learned, we can produce any function of 2 operands. If we have more than 2 operands, we can link them together into a boxed list using dyad ; and then extract the pieces as needed using dyad {:: . For the rest of this chapter we will show examples of functions turned into tacit verbs using hooks and forks. If I don't show the expansion using the bident/trident rules, you should produce it yourself.

To find how much x has changed from y, as a percentage of y :

pctchg =: 100&*@:(- % ])

12 pctchg 10

20

This becomes 100 * (x - y) % y . Note the use of ] to select the original y operand. Similarly, [ can be used to select the original x operand. Tacit verbs make heavy use of [ and ] .

Another way to code pctchg is

pctchg =: 100"_ * (- % ])

where we used the constant verb 100"_ which produces 100 no matter what its operands are. Which of these forms you prefer is a matter of taste.

fndisplay is very helpful in understanding tacit verbs. The two versions of pctchg are displayed as

defverbs 'times"0 minus"0 div"0'

defnouns 'x y'

x 100&times@:(minus div ]) y

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

|100 times (x minus y) div y|

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

x (100"_ times (minus div ])) y

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

|100 times (x minus y) div y|

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

I encourage you to use fndisplay to expand any tacit definitions that are troublesome.

To find the elements common to x and y, keeping the same order as in x :

setintersect =: e. # [

3 1 4 1 5 9 setintersect 4 6 9

4 9

This becomes (x e. y) # x . You can see that the identity verbs [ and ] are useful for steering operands through hooks and forks. As an exercise, see how the alternative version ([ -. -.) produces the same result.

To list all the indexes of the 1s in a Boolean list:

booltondx =: (# i.@:#)"1

booltondx 0 1 0 1 0 0 1

1 3 6

Note that we are careful to give our verb a rank of 1, since it works only with lists. The primitive I. has the same effect.

To find the difference between the largest and smallest items in a list:

range =: (>./ - <./)"1

range 3 1 4 1 5 9

8

To find the index of the largest item in a list:

indexmax =: (i. >./)"1

indexmax 3 1 4 1 5 9 2 6 5 3 5

5

To create a Boolean list with a 1 at each position that is different from the previous position:

changeflag =: 1: , 2: ~:/\ ]

changeflag 1 1 2 2 7 7 7 3 3 4 5 8 8

1 0 1 0 1 0 0 1 0 1 1 1 0

We could have done this without using forks, with (1&,)@:(2&(~:/\) . Which version you use is a matter of taste. The number verbs _9: through 9: are very useful if you like forks rather than long conjunction chains. Note that changeflag is executed as if parenthesized (1: , (2: (~:/\) ])) . If you work with long trains of verbs like this you will soon notice that if you count the verbs from the right (starting at 0, of course), the even-numbered verbs have the original x and y applied, and the odd-numbered ones combine the results from the even-numbered.

To replace multiple spaces by a single space:

delmb =: ((#~ -.) ' '&E.)"1

delmb 'abc nb'

abc nb

To create an array in which each item is a list of (value, number of times that value appeared):

histogram =: ~. ,. #/.~

histogram 3 1 4 1 5 9 2 6 5 3 5 8 9 7 9

3 2

1 2

4 1

5 3

9 3

2 1

6 1

8 1

7 1

To append the contents of the first item of x in front of y and the contents of the last item of x behind y :

enclose =: >@:{.@:[ , ] , >@:{:@[

'*' enclose 'xyz'

*xyz*

'()' enclose 'abc'

(abc)

To produce 1 if all the items of y are equal, 0 if not

allitemsequal =: -: 1&|.

allitemsequal 1 1 1 1 1

1

allitemsequal 1 1 1 2 1

0

To extend x to the length of y, where the items of y supply default values for the corresponding omitted items of x :

default =: [ , (}.~ #)~

('abc';2) default 'Defname';0;'Deftype';100

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

|abc|2|Deftype|100|

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

('abc';2;'xyz';30) default 'Defname';0;'Deftype';100

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

|abc|2|xyz|30|

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

The verb [:, which we met as a way to cause an error, has a special meaning in a fork. As the leftmost verb of the fork, [: means 'ignore the left branch'. So, Nx ([: V1 V2) Ny is V1 Nx V2 Ny and ([: V1 V2) Ny is V1 V2 Ny . In both cases, ([: V1 V2) is equivalent to V1@:V2 . Almost always, the choice between one form or the other is a matter of taste. Do not fear that the extra word in the fork leads to slower execution; the [: is not executed—it is recognized by the parser when it creates the anonymous verb for the fork.

As a final example, here is a definition of the word-counting example we wrote earlier. See if you can convince yourself that it is equivalent to wc2 :

WS =: ' ',TAB,LF

wc3 =: (# , (*. -.@(|.!:0))@(e.&WS) , +/@(LF&=))@ReadFile

I could go on with pages more of hooks and forks for you to study, but what you really need is to write some yourself. It will be a frustrating experience for the first few weeks as you struggle to jigsaw your verbs into pieces that have one or two operands and yet fit together to perform a complex function. It's not a necessary skill—you can get along acceptably in J writing mostly explicit definitions, with an occasional fork thrown in where obvious—but it is a useful, honorable, and satisfying one. Learning to write tacit verbs is like learning to walk with a book balanced on your head: it will slow you down at first, but in the end you'll stand taller for it.

The book J Phrases, which is part of the J release, has dozens of interesting examples of tacit programs which you can use as exercises.

Tacit and Compound Adverbs

Adverbs as well as verbs can be defined tacitly. Any sequence of adverbs is also an adverb, and applies the component adverbs one by one to its left operand. For example,

onprefixes =: /\

defines an adverb that applies / followed by \, as can be seen by

+ onprefixes 1 2 3 4

1 3 6 10

A conjunction with one operand also defines an adverb. A conjunction needs two operands, but if you supply one, the combination is treated as an adverb that attaches its operand to the empty side of the conjunction. For example, (2&) is an adverb, and + (2&) is equivalent to 2&+ . For another example, the J startup scripts define

each =: &.>

so that

>: each 1 2 3

is equivalent to

>:&.> 1 2 3

2 3 4

Referring To a Noun In a Tacit Verb

Suppose you want a verb v that returns the current value of the noun n (maybe it's a button handler). Suppose you want v to be tacitly defined. How would you do it? You can't use

v =: n

because that would use the value of n at the time v is defined (in fact, v would be a noun), and you want the value of n when v is executed. Use this trick:

v =: ".@:('n'"_)

When this is executed, the operand is ignored and replaced by the string 'n', which is then executed by ". to produce the value of the noun n .

<< >> Contents Help