Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Rich H.J for C programmers.2006.pdf
Скачиваний:
19
Добавлен:
23.08.2013
Размер:
1.79 Mб
Скачать

31. Writing Your Own Modifiers

If you find that you are coding recurring patterns of operations, you can write a modifier that represents the pattern. You will find that reading your code is easier when the patterns are exhibited with names of your choosing.

You write a modifier like you write a verb, using conjunction define or adverb define, or 2 :n or 1 :n for one-liners. When you assign the modifier to a name, that name becomes a conjunction or adverb, and it will be invoked as

u name v y (monad) or x u name v y (dyad) if it is a conjunction, or u name y (monad)or x u name y (dyad) if it is an adverb.

When a modifier is invoked, the lines of the modifier are executed one by one, just as when a verb is invoked, and the result of the last sentence executed becomes the result of the modifier. The u (and v, for conjunctions) operand(s) of the modifier are assigned to the local names u. (and v.) when the modifier starts execution (in addition, if u is a noun, it is assigned to the local name m. and if v is a noun it is assigned to n.)

User-written modifiers are of two types: those that refer to the variables x. and y., and those that do not.

Modifiers That Do Not Refer To x. Or y.

If the modifier does not refer to x. or y., its text is interpreted when its operands (u and, for conjunctions, v) are supplied, and its result is an entity which may be any of the four principal parts of speech. The result replaces the modifier and its operands in the sentence, and execution of the sentence continues.

For example, if the explicitly-defined conjunction c, which does not refer to x. or y., is invoked as

x u c v y

the sequence u c v is evaluated to produce a resulting verb, and then that verb is executed with x and y as operands. The text of c is executed without reference to x and y .

Usually you will want your modifier to produce a verb, but nothing keeps you from writing a conjunction whose result is, for example, another conjunction. Here we will confine ourselves to verb results.

Let's write some of the utility modifiers referred to in earlier chapters. Ifany was an adverb that executed u if y had a nonzero number of items:

9!:3 (5) NB. Do this once to select simplified display Ifany =: 1 : 'u. ^: (*@#@])'

< Ifany <^:(*@#@])

Ifany does not need to look at y.; it creates a verb that executes u only if y has items. Here we have executed the adverb Ifany with the left operand <, and the result is a

177

verb—the compound verb <^:(*@#@]) . We can execute that verb on a noun operand:

< Ifany 1 2 3 +-----+ |1 2 3| +-----+

Remember that Ifany is an adverb, so it has precedence and the line is executed as if

(< Ifany) 1 2 3 . The verb (< Ifany), which has the value <^:(*@#@]), is applied to 1 2 3 and produces the boxed result.

< Ifany ''

An empty y is left unboxed.

u Butifnull n was a conjunction that applied u if y had items, otherwise it produced a result of n . It could be written:

Butifnull =: 2 : 'n."_ ` u. @. (*@:#@:])'

Again (*@:#@:]) will check whether y has items, and this time the result will be used to select the appropriate verb to execute.

< Butifnull 5 5"_`<@.(*@:#@:])

When Butifnull is executed with operands, it produces a verb.

< Butifnull 5

'abc'

+---+

 

|abc|

 

+---+

''

< Butifnull 5

5

 

The verb it produces can be applied to its own noun operands.

Example: Creating an Operating-System-Dependent Verb

The great thing about modifiers that do not refer to x. or y. is that they are fully interpreted before the x and y operands are supplied, so there is no interpretive overhead during the processing of the data. Here is a more complex example taken from the J system. The goal is to define a verb playsound that can be used to play a .wav file under Windows:

NB. y. is the file data to be played playsound =: '' adverb define select. 9!:12 NIL

case. 2 do.

'winmm.dll sndplaysound i *c i' & (15!:0) @ (;&1) case. 6 do.

NB. 2=nodefault + 4=memory + 16b20000 = file 'winmm.dll PlaySound i *c i i' & (15!:0) @ (;&(0;4))

end.

)

To begin with, let's make sense of this odd sequence '' adverb define . The adverb define defines an adverb, but what's the ''? Simple—it's the left argument

178

to the adverb that was defined: the adverb is executed with u. set to '' . The result of that execution of the adverb is what gets assigned to playsound .

So, what happens when the adverb is executed? The adverb calls the foreign 9!:12 to see what operating system is running, and executes a selected line that contains the definition of a compound verb. Since that line is the last one executed, it becomes the result of the adverb; so the result of the adverb is the selected verb, and that is what is assigned to playsound . On my system, this leaves playsound defined as a single compound verb:

playsound

'winmm.dll PlaySound i *c i i'&(15!:0)@(;&(0;4))

Lovely! No check for operating system needs to be made when I invoke playsound; the check was made when playsound was defined.

Example: The LoopWithInitial Conjunction

The conjunction LoopWithInitial that we learned about earlier can be written as

LoopWithInitial =: 2 : 'u.&.>/\.&.(,&(<v.))&.|.&.(<"_1)'

It's just one application of &. after another. We can use it to illustrate a subtlety about modifiers that you should be aware of. Consider an invocation of

LoopWithInitial : vb =. +

init =. 4 5

vb LoopWithInitial init vb&.>/\.&.(,&(<4 5))&.|.&.(<"_1)

The verb that is produced seems in order, but notice one point: the verb contains the value of init, but the name of vb . This is a rule: the name of a verb argument is passed into a modifier, but the value of a noun argument is passed. Note that if these lines appear inside a verb, the verb vb, which is assigned by private assignment, is not defined inside LoopWithInitial, because LoopWithInitial is running in a different explicit definition from the one in which vb was assigned. As we see above,

LoopWithInitial can pass vb into other modifiers, but if LoopWithInitial tried to execute vb it would fail.

Before we move on I want to point out one tiny example of the beauty of J. For &.(,&(<4 5)) to work, there must be some obverse of ,&(<4 5) that undoes its effect. What would that be? We can see what the interpreter uses:

,&(<4 5) b. _1 }: :.(,&(<4 5))

It undoes the addition of a trailing item with }: which discards the last item. Yes, that makes sense (the obverse has its own obverse which is the original verb).

Example: A Conjunction that Analyzes u and v

The conjunction u&.v expresses with great clarity the sequence of applying a transformation v, then applying the operation u, then inverting the transformation v . The dyad x u&.v y applies the same transformation to both x and y, but in many cases the transformation is meaningful only on one operand, and what we would like is a

179

conjunction Undery such that x u Undery v y produces v^:_1 x u v y . For example, to encipher the characters of y by replacing each one by the letter x positions earlier, we would use

5 1 3 2 -~ Undery ('abcdefghijkl'&i."0) 'hijk' to perform the function

t =. 'abcdefghijkl'&i."0 'hijk' t =. 5 1 3 2 -~ t

t { 'abcdefghijkl' chgi

With that x stuck in the middle of the desired result v^:_1 x u v y it appears that we will have to refer to x. in our conjunction, but actually we can use an advanced feature of J to make the x. disappear. The sequence (u v) produces a verb that, when executed as the dyad x (u v) y, gives the result of x u v y (you will learn about this and more if you persevere with the part of the book devoted to tacit programming). So, the verb we are looking for is v^:_1 @: (u v) and we can write

Undery =: 2 : 'v.^:_1 @: (u. v.)'

5 1 3 2 -~ Undery ('abcdefghijkl'&i."0) 'hijk' chgi

Before we pat ourselves on the back for this achievement, we should consider whether the verb produced by Undery has the proper rank. We see that it does not: Undery applies v to the entire y, and u to the entire x and the result of u y, when really we should be performing the operation on cells of x and y, where the cell-size of x is given by the left rank of u and the cell-size of y is given by the right rank of v . For example, if we wanted to take the -x least-significant bits of y, we could use

_3 {."0 1 Undery #: 30

6

(remember that monad #: converts an integer y to its binary representation, producing a Boolean list–we are taking x bits of that and then converting back to integer) The binary code for 30 is 11110, the 3 low-order bits are 110, and the result is 6. But when we have list arguments, we get an incorrect result:

_3 _4 _3 {."0 1 Undery #: 32 31 30 0 15 12

The result for 30 is wrong: because #: was applied to the entire y, 110 was extended with framing fills to become 1100, and the result is 12 instead of the expected 6. To get the right result we need to apply the verb to cells of the correct size:

_3 _4 _3 ({. Undery #:"0) 32 31 30 0 15 6

and naturally we would like to make Undery automatically produce a verb with the correct rank.

The way to find the rank of the verb u is to execute u b. 0 . In our conjunction u. and v. are verbs, and we can use their ranks to produce an Undery that gives the correct rank:

180