Applying an adverb to a list of gerunds - j

Consider a list of gerunds and some data we wish to apply them to, cyclically:
ms=.*`+`- NB. list of gerunds
d =.3 4 5 6 NB. some data
We can do:
ms/ d NB. returns 9, ie, the result of 3 * 4 + 5 - 6
Now we pose the question: how does the result change if we change the order in which we apply the verbs? That is, we consider all 6 possible orders:
allms=. (A.~i.#!##) ms
which looks like:
┌─┬─┬─┐
│*│+│-│
├─┼─┼─┤
│*│-│+│
├─┼─┼─┤
│+│*│-│
├─┼─┼─┤
│+│-│*│
├─┼─┼─┤
│-│*│+│
├─┼─┼─┤
│-│+│*│
└─┴─┴─┘
To answer the question, we can do:
allms (4 : 'x/ y')"1 d
NB. returns 9 _21 _1 _23 _41 _31
But notice I was forced to use an anonymous, non-tacit verb to accomplish this. Because in order to apply the adverb /, I had to have a named verb. When what I really wanted to do is treat / like a rank 1 verb and "map" it over my list allms, something in spirit like the illegal formulation:
/&d"1 allms NB. This is invalid J
That is, for each gerund on the list, transform it with the adverb / and apply it to the data d.
J seems to resist this higher-order "treating verbs like data" thinking. So I want to know what the natural J way of approaching this problem would be.
To be specific, you are given the list of gerunds ms and data d, as defined above. The task is to create a verb that returns a list of the results ms/ d, for every possible ordering of ms (ie, a verb that returns 9 _21 _1 _23 _41 _31 in our example). The verb must be a tacit verb.

You don't really want that
There are fundamental syntactic reasons why you can't tacitly slice and dice arguments to operators (adverbs and conjunctions).
Without going into detail, allowing operators to be modified by other operators, like your proposed / modified with "1, would require a fundamental restructuring of J's grammar. And there would be major tradeoffs, in particular to simplicity and expressiveness (i.e. notational elegance)¹,².
So, if you want to distribute operators over gerunds like this, you'll have to write utilities for it, and the most straightforward way by far is by using explicit code. One pre-packaged utility to consider in this domain is the doog script, available in the J Wiki and SVN repo.
Here it is anyway
However, the doog script, like your approach, is fundamentally explicit³.
So if you really want to achieve these ends tacitly:
D =. foo`bar`baz
t =. D / (#:]) NB. Here's our "over" (/)
over =. [^:(D -: ]) L: (L.D) & (5!:1<,'t')
allOver =: (]^:[~ 1:`'' , over f.)~
3 4 5 6 allOver"1~ (A.~i.#!##) *`+`- NB. Note the "1
9 _21 _1 _23 _41 _31
I warned you
Without getting into too much detail, the trick here is using the verb ]^:[ to allow ^: to execute an arbitrary atomic representation as input.
That is, some_atomic_rep f^:[ data turns into f^:some_atomic_rep data, which, for a suitable atomic rep, can execute anything at all, while using all the argument-processing goodness available to verbs (in particular, rank).
The rest is just an elegant (read: lazy) way to turn your gerundial inputs (whichever parts you make available to the verb with rank or other argument-selection mechanisms) into an atomic rep suitable for a right-hand argument to ^:.
The meat of it is we have the template D / (#:]) and we replace D with the gerund of your choice (the #:] is necessary because by the time the gerund gets executed, it'll have two inputs: your actual input, d, as well as itself, D)4.
Lasciate ogne speranza
To visit the Ultima Thule of these wicked follies, check out the discovery of dont in J, which is just like do (".), except ... really, you shouldn't.
¹ As a quick example: puzzle out what this would mean for precedence between wordclasses.
² Having said that, Jose "Pepe" Quintana, the leader of the underground J club F^4 (The Fully Fixable Functional Faction), once found a backdoor that actually did allow operators to take other operators as inputs. See this message in the "J Myths Puzzles" thread from 2008 (scroll past all the spoiler-hiding blank lines). Of course, once he mentioned it, Roger took notice, and immediately plugged the gap.
³ The way I once put it was "Yes, dcog is ugly, but I like to think of it as Messiah Code: it's ugly so that other code doesn't have to be. A sponge for sin".
4 Take note, the template gerund foo`bar`baz can be anything you like, of any length, using any names. It doesn't matter. What does matter is that the names you use are either proverbs or undefined (which the interpreter treats like proverbs, by design). Using pronouns or pro-operators will break stuff. Alternatively, you could use another kind of noun, like simply __ or something (which I find mnemonic for fill in the ____).

ms=.*`+`- NB. list of gerunds
d =.3 4 5 6 NB. some data
allms=. (A.~i.#!##) ms
I'd start by "treating my verbs like data" using strings to represent the gerunds
*`+`-
append the '/'character and then use 128!:2 (Apply) which takes a string describing a verb as its left argument and applies it to the noun that is its right argument. of course to do this you need to make allms into verb strings.
That can be done using:
[ ger=. ,&'/' # }: # (1j1 #!.'`' ;)"1 allms
*`+`-/
*`-`+/
+`*`-/
+`-`*/
-`*`+/
-`+`*/
Then using 128!:2 (Apply)
ger 128!:2 d
9 _21 _1 _23 _41 _31
As a one line tacit verb
gvt=. ,&'/'# }:#(1j1 #!.'`' ;)"1 #: [ 128!: 2 ]
allms gvt d
9 _21 _1 _23 _41 _31
I rarely play these games, so I am not saying this is the best approach, but it works.

Related

How to give 2 arguments to a "Do-While"?

the problem
This is a recurring difficulty I encounter when I reach for a (u^:v^:_ y) (Do-While) construct.
As context, it was inspired by this code golf question, which asks you to build up a string from integers, starting with 0, appending the stringified form of the next integer, until the length reaches the number given.
For example, if we stop at length 5, we build up the string 01234 (which has length 5). The last number appended was 4, hence we return 4 as our answer. If we were to stop at length 12, we would produce the 12-character string 012345678910, and hence return 10.
ugly solution
Here is a helper verb len which, given a list of integers, will return the total length of their representation as a ''-joined string:
len =. +/ #: (##":"0)
The following expression will then return the correct puzzle answer for input 12:
(>: ^: (<&12#len#:i.#>:) ^:_) 0 NB. returns 10
question
The problem is that I have hard-coded my input 12 into the expression, rather than accepting it as an argument to the tacit verb.
Yet I don't see how to correct this. The "Do" verb -- the incrementing counter -- is the one that receives the argument. Since it always starts at 0, it should be hardcoded, rather than hardcoding the stopping length. But because of the semantics of "Do-While", I don't see how I'd do that, because it accepts an argument to the main verb u, not the condition verb v.
Is there an elegant way to solve this?
Would standard recursion with $: and #. be a good strategy?
I know I could make the argument a list containing both the "stopping length" and the counter, but that will just clutter the code with bookkeeping verbs like {. and {: as I tease apart and piece together the list on each iteration.
bonus question
In the notes for "Do-While", NuVoc warns, "Despite its name, Do-While is very rarely used in J. Almost all looping is done using the other modifiers", suggesting there might be another approach altogether.
I don't see how that's possible in this case without the unacceptable method of first guessing at an upper-bound for the answer, but if there is, I'd like to know.
One solution is to provide the upper-bound as the left argument. You need to adjust your "Do" and "While" verbs to use the left & right arguments appropriately but, if I understand your question correctly, the following seems to work:
12 (>:#] ^: ([ > len#:i.#>:#]) ^:_) 0
10
If you want to "fix" the 0 starting point you could bind it as the right argument:
(>:#] ^: ([ > len#:i.#>:#]) ^:_)&0 ] 12
10
In the interests of golfing you could incorporate the len verb into the current expression and shorten to this:
(>:#]^:(>' '##-.~":#i.#>:#])^:_)&0 ] 12
10

Memoization in J

Every time I use J's M. adverb, performance degrades considerably. Since I suspect Iverson and Hui are far smarter than I, I must be doing something wrong.
Consider the Collatz conjecture. There seem to be all sorts of opportunities for memoization here, but no matter where I place M., performance is terrible. For example:
hotpo =: -:`(>:#(3&*))#.(2&|) M.
collatz =: hotpo^:(>&1)^:a:"0
##collatz 1+i.10000x
Without M., this runs in about 2 seconds on my machine. With M., well, I waited over ten minutes for it to complete and eventually gave up. I've also placed M. in other positions with similarly bad results, e.g.,
hotpo =: -:`(>:#(3&*))#.(2&|)
collatz =: hotpo^:(>&1)M.^:a:"0
##collatz 1+i.10000x
Can someone explain the proper usage of M.?
The M. does nothing for you here.
Your code is constructing a chain, one link at a time:
-:`(>:#(3&*))#.(2&|)^:(>&1)^:a:"0 M. 5 5
5 16 8 4 2 1
5 16 8 4 2 1
Here, it remembers that 5 leads to 16, 16 leads to 8, 8 leads to 4, etc... But what does that do for you? It replaces some simple arithmetic with a memory lookup, but the arithmetic is so trivial that it's likely faster than the lookup. (I'm surprised your example takes 10 whole minutes, but that's beside the point.)
For memoization to make sense, you need to be replace a more expensive computation.
For this particular problem, you might want a function that takes an integer and returns a 1 if and when the sequence arrives at 1. For example:
-:`(>:#(3&*))#.(2&|)^:(>&1)^:_"0 M. 5 5
1 1
All I did was replace the ^:a: with ^:_, to discard the intermediate results. Even then, it doesn't make much difference, but you can use timespacex to see the effect:
timespacex '-:`(>:#(3&*))#.(2&|)^:(>&1)^:_"0 i.100000'
17.9748 1.78225e7
timespacex '-:`(>:#(3&*))#.(2&|)^:(>&1)^:_"0 M. i.100000'
17.9625 1.78263e7
Addendum: The placement of the M. relative to the "0 does seems to make
a huge difference. I thought I might have made a mistake there, but a quick test showed that swapping them caused a huge performance loss in both time and space:
timespacex '-:`(>:#(3&*))#.(2&|)^:(>&1)^:_ M. "0 i.100000'
27.3633 2.41176e7
M. preserves the rank of the underlying verb, so the two are semantically equivalent, but I suspect with the "0 on the outside like this, the M. doesn't know that it's dealing with scalars. So I guess the lesson here is to make sure M. knows what it's dealing with. :)
BTW, if the Collatz conjecture turned out to be false, and you fed this code a counterexample, it would go into an infinite loop rather than produce an answer.
To actually detect a counterexample, you'd want to monitor the intermediate results until you found a cycle, and then return the lowest number in the cycle. To do this, you'd probably want to implement a custom adverb to replace ^:n.

When does #: give more desirable behavior than #.^:_1

If you give the inverse of Base (#.^:_1) a list as the left argument, it will produce the same result as Antibase (#:):
24 60 (#.^:_1) 123456
17 36
24 60 (#:) 123456
17 36
If you give Antibase (#:) a single left argument, it duplicates the Residue (|), rather than the inverse of Base (#.^:_1):
8 #: 1234
2
8 | 1234
2
8 (#.^:_1) 1234
2 3 2 2
Under what circumstances would the behavior of Antibase be superior to an inverted Base? And why wouldn't you just use Residue in those places? I feel like I must be missing something obvious about the utility of Antibase's behavior.
To start with: the J Dictionary defines #.^:_1 to be equivalent to #:, so it shouldn't be surprising that they're (mostly) interchangeable. In particular, the Vocabulary page for #: says :
r&#: is inverse to r&#."
And this theoretical equivalence is also supported in practice. If you ask the implementation of J for the its definition of #.^:_1, using the super-cool adverb b., you'll get:
24 60 60&#. b._1
24 60 60&#:
Here, we can see that all #.^:_1 is doing is deferring to #:. They're defined to be equivalent, and now we can see #.^:_1 -- at least in the case of a non-scalar LHA¹ -- is simply passing its arguments through to #:.
So how do we explain the discrepancy you observed? Well it turns out that, even in the pure halls of J, theory differs from practice. There is an inconsistency between dyads #: and #.^:_1 and, at least in the case of scalar left arguments, the behavior of the latter is superior to the former.
I would (and have) argue that this discrepancy is a bug: the Dictionary, quoted above, states the two dyads are equivalent, but that assertion is wrong when 0-:#$r (i.e. r is a scalar). Take r=.2 for example: (r&#: -: r&#.^:_1) 1 2 3 does not hold. That is, if the Dictionary's assertion (quoted above) is true, that statement should return 1 (true), but it actually returns 0 (false).
But, as you pointed out, it is a useful bug. Which is to say: I'd prefer the definition of #: were changed to match #.^:_1, rather than vice-versa. But that's the only time #.^:_1 is more convenient than #:. In all other cases, they're equivalent, and because #: is a primitive and #.^:_1 is compound phrase with a trailing _1, the former is much more convenient.
For example, when your right-hand argument is a numeric literal, it's easy to get that inadvertently attached to the _1 in #.^:_1, as in 2 2 2 2 #.^:_1 15 7 4 5, which will raise an error (because _1 15 7 4 5 is lexed as a single word, and therefore taken, as a whole, to be the argument to ^:). There are ways to address this, but none of them are as convenient or simple as using #:.
You could make a counterargument that in most cases, the LHA will be a scalar. That's an empirical argument, which will vary from codebase to codebase, but I personally see a lot of cases like 24 60 60 #: ..., where I'm trying to break up timestamps into duration buckets (hours, minutes, seconds), or (8#2)#: ..., where I'm trying explode bytes into exactly 8-bit vectors (contrasted to, e.g., 8 #.^:_1 ..., which will break bytes into as many bits as it takes, whether that's 8 or 3 or 17¹). And I'd further argue that in the J community, these are both commonly-used and instantly-recognizable idioms, so the use of #: assists with clarity and team communication.
But, bugs notwithstanding, ultimately #: and #.^:_1 are defined to be equivalent, so which one you use is really a matter of taste. (Then why define #.^:_1 at all, you ask? Well, that's a whole 'nother story.)
¹ PS: Wanna see something cool? How does #.^:_1 achieve its magic for scalar LHAs? Let's just ask J!
2&#. b._1
($&2#>:#(2&(<.#^.))#(1&>.)#(>./)#:|#, #: ]) :.(2&#.)
First off, notice the (by now) completely unsurprising use of #:. All #.^:_1 is doing is calculating the appropriate LHA for #:.
Second, the phrase $&2#>:#(2&(<.#^.))#(1&>.)#(>./)#:|#, shows you how J calculates the number of digits required to represent (the maximum value of) the y in the base (or radix) x. And it's a useful phrase unto itself, so much so that I keep a version of it around in my personal utility library:
ndr =: 10&$: :(>:#<.#^. (1 >. >./#:|#,))
ndr 1 10 100 101 NB. Number Digits Required in [default] base 10
3
16 ndr 1 10 100 101 NB. Number Digits Required in hexadecimal
2
Perhaps not an overwhelmingly compelling application but,
(4 # 256) #: 8234092340238420938420394820394820349820349820349x
is 10x faster than
256 #. inv (2^32x) | 8234092340238420938420394820394820349820349820349x

Chain verbs in J

Suppose a boxed matrix containing various types:
matrix =: ('abc';'defgh';23),:('foo';'bar';45)
matrix
+---+-----+--+
|abc|defgh|23|
+---+-----+--+
|foo|bar |45|
+---+-----+--+
And a column descriptor:
columnTypes =: 'string';'string';'num'
I want to apply verbs on this matrix by column according to types. I'll be using verbs DoString and DoNum:
chain =: (('string';'num') i. columnTypes) { DoString`DoNum
EDIT: The column descriptors are important, the decision on which verb to use is based on them, not on the type itself. In reality, I could have several types of strings, numerics, and even dates (which would be numeric in J).
How do I apply chain to each row of matrix? The verbs themselves can take care of whether the passed value is boxed or not, that's fine. Also, I'd rather avoid transposing the matrix (|:) as it could be quite large.
The standard method for doing this is:
Convert your row (cell)-oriented structure to a column-oriented structure
Apply the correct verb to each column (just once)
Step (1) is easy. Step (2) is also easy, but not as obvious. There's a little trick that helps.
The trick is knowing that a number of primitive operators accept a gerund as a left argument and produce a function which cycles over the gerund, applying each verb in turn. IMO, the most useful operator in this category is ;. . Here's an example implementation using it:
Step (0), inputs:
matrix =: ('abc';'defgh';23),:('foo';'bar';45)
columnTypes =: 'string';'string';'num'
DoString =: toupper
DoNum =: 0&j.
matrix
+---+-----+--+
|abc|defgh|23|
+---+-----+--+
|foo|bar |45|
+---+-----+--+
Step (1), columify data:
columnify =: <#:>"1#:|: :. rowify =: <"_1&>
columnify matrix
+---+-----+-----+
|abc|defgh|23 45|
|foo|bar | |
+---+-----+-----+
Note that the columnify is provided with an inverse which will re-"rowify" data, though you shouldn't do that: see below.
Step (2), apply the correct verb to each column (exactly once), using the verb-cycling feature of ;.:
homogenize =: ({. foo&.>#:{.`'') [^:('foo'-:])L:0~ ]
chain =: DoString`DoNum`] homogenize#{~ ('string';'num')&i.
Note that the default transformation for unknown column-types is the identity function, ].
The verb homogenize normalizes the input & output of each column-processor (i.e abstracts out the pre- and post-processing so that the user only has to provide with the dynamic "core" of the transformation). The verb chain takes a list of column-types as an input and derives a gerund appropriate for use a left-hand argument to ;. (or a similar operator).
Thus:
1 (chain columnTypes);.1 columnify matrix
+---+-----+---------+
|ABC|DEFGH|0j23 0j45|
|FOO|BAR | |
+---+-----+---------+
Or, if you really must have an NxM table of boxed cells, apply the cut "under" columnify:
1 (chain columnTypes);.1&.columnify matrix
+-----+-----+
|ABC |FOO |
+-----+-----+
|DEFGH|BAR |
+-----+-----+
|0j23 |0j45 |
+-----+-----+
But note it is much more appropriate, in a J context, to keep the table as a list of homogeneous columns, for both performance and notational reasons.
J works best when processing arrays "in toto"; the rule of thumb is you should let primitive or user-defined name see as much data as possible at each application. That's the major benefit of this "columificaton" approach: if you store your data as a list of homogeneous columns, it will be faster and easier to manipulate later.
However, if your use-case really demands you keep the data as a NxM table of boxed cells, then converting your data to- and from- column normal form is an expensive no-op. In that case, you should stick with your original solution,
1 chain\"1 matrix
which (because you asked) actually works on the same premise as the ;. approach. In particular, \ is another of those primitive operators which accepts a gerund argument, and applies each verb in succession (i.e. to each a new window of data, cyclically).
In effect, what 1 chain\"1 matrix does is break the matrix into rows ("1), and for each row, it creates a 1-wide moving window, (1 f\ matrix), applying the verbs of chain to each of those 1-wide windows cylically (i.e. f changes with every 1-wide data window of each row of the matrix).
Since the moving 1-window of a row (a rank-1 vector) is the atoms of the row, in order, and the verbs of chain are given in the same order, in effect you're applying those verbs to the columns of the matrix, one. atom. at. a. time.
In short: 1 chain\"1 matrix is analogous to foo"0 matrix, except foo changes for each atom. And it should be avoided for the same reason foo"0 matrix should be avoided in general: because applying functions at small rank works against the grain of J, incurring a performance penalty.
In general, it's better to use apply functions at higher ranks whenever you can, which in this case calls for converting (and maintaining) matrix to column-normal form.
In other words, here, ;. is to "1 as \ is to "0. If you find the whole columnify/homogenize thing too lengthy or bulky (compared to 1 chain\"1 matrix), you can import the script provided at [1], which packages up those definitions as re-usable utilities, with extensions. See the page for examples and instructions.
[1] Related utility script:
http://www.jsoftware.com/jwiki/DanBron/Snippets/DOOG
If these calculations depend only on the data inside individual boxes (and, perhaps, global values,) it is possible to use Agenda with Under Open (aka Each). An application of this technique is shown below:
doCells =: (doNum`doString #. isLiteral)&.>
isLiteral=: 2 -: 3!:0
doNum =: +: NB. Double
doString =: toupper
doCells matrix
┌───┬─────┬──┐
│ABC│DEFGH│46│
├───┼─────┼──┤
│FOO│BAR │90│
└───┴─────┴──┘
(In this example I've put in arbitrary meanings for doNum and doString to help make the viability plain.)
The version of isLiteral used here may well suffice, but it will fail if either sparse literal or unicode values will be involved.
If the calculations need to involve more of the matrix than a single box, this won't be the answer to your question. If calculation needs to occur by line, instead, the solution may involve applying a verb at rank _1 (i.e. to each item of the highest axis.)
Through experimentation, I get this to do what I want:
1 chain\"1 matrix
Now to understand it...

Best strategies for reading J code

I've been using J for a few months now, and I find that reading unfamiliar code (e.g. that I didn't write myself) is one of the most challenging aspects of the language, particularly when it's in tacit. After a while, I came up with this strategy:
1) Copy the code segment into a word document
2) Take each operator from (1) and place it on a separate line, so that it reads vertically
3) Replace each operator with its verbal description in the Vocabulary page
4) Do a rough translation from J syntax into English grammar
5) Use the translation to identify conceptually related components and separate them with line breaks
6) Write a description of what each component from (5) is supposed to do, in plain English prose
7) Write a description of what the whole program is supposed to do, based on (6)
8) Write an explanation of why the code from (1) can be said to represent the design concept from (7).
Although I learn a lot from this process, I find it to be rather arduous and time-consuming -- especially if someone designed their program using a concept I never encountered before. So I wonder: do other people in the J community have favorite ways to figure out obscure code? If so, what are the advantages and disadvantages of these methods?
EDIT:
An example of the sort of code I would need to break down is the following:
binconv =: +/# ((|.#(2^i.###])) * ]) # ((3&#.)^:_1)
I wrote this one myself, so I happen to know that it takes a numerical input, reinterprets it as a ternary array and interprets the result as the representation of a number in base-2 with at most one duplication. (e.g., binconv 5 = (3^1)+2*(3^0) -> 1 2 -> (2^1)+2*(2^0) = 4.) But if I had stumbled upon it without any prior history or documentation, figuring out that this is what it does would be a nontrivial exercise.
Just wanted to add to Jordan's Answer : if you don't have box display turned on, you can format things this way explicitly with 5!:2
f =. <.#-:##{/:~
5!:2 < 'f'
┌───────────────┬─┬──────┐
│┌─────────┬─┬─┐│{│┌──┬─┐│
││┌──┬─┬──┐│#│#││ ││/:│~││
│││<.│#│-:││ │ ││ │└──┴─┘│
││└──┴─┴──┘│ │ ││ │ │
│└─────────┴─┴─┘│ │ │
└───────────────┴─┴──────┘
There's also a tree display:
5!:4 <'f'
┌─ <.
┌─ # ─┴─ -:
┌─ # ─┴─ #
──┼─ {
└─ ~ ─── /:
See the vocabulary page for 5!: Representation and also 9!: Global Parameters for changing the default.
Also, for what it's worth, my own approach to reading J has been to retype the expression by hand, building it up from right to left, and looking up the pieces as I go, and using identity functions to form temporary trains when I need to.
So for example:
/:~ i.5
0 1 2 3 4
NB. That didn't tell me anything
/:~ 'hello'
ehllo
NB. Okay, so it sorts. Let's try it as a train:
[ { /:~ 'hello'
┌─────┐
│ehllo│
└─────┘
NB. Whoops. I meant a train:
([ { /:~) 'hello'
|domain error
| ([{/:~)'hello'
NB. Not helpful, but the dictionary says
NB. "{" ("From") wants a number on the left.
(0: { /:~) 'hello'
e
(1: { /:~) 'hello'
h
NB. Okay, it's selecting an item from the sorted list.
NB. So f is taking the ( <. # -: # # )th item, whatever that means...
<. -: # 'hello'
2
NB. ??!?....No idea. Let's look up the words in the dictionary.
NB. Okay, so it's the floor (<.) of half (-:) the length (#)
NB. So the whole phrase selects an item halfway through the list.
NB. Let's test to make sure.
f 'radar' NB. should return 'd'
d
NB. Yay!
addendum:
NB. just to be clear:
f 'drara' NB. should also return 'd' because it sorts first
d
Try breaking the verb up into its components first, and then see what they do. And rather than always referring to the vocab, you could simply try out a component on data to see what it does, and see if you can figure it out. To see the structure of the verb, it helps to know what parts of speech you're looking at, and how to identify basic constructions like forks (and of course, in larger tacit constructions, separate by parentheses). Simply typing the verb into the ijx window and pressing enter will break down the structure too, and probably help.
Consider the following simple example: <.#-:##{/:~
I know that <. -: # { and /: are all verbs, ~ is an adverb, and # is a conjunction (see the parts of speech link in the vocab). Therefore I can see that this is a fork structure with left verb <.#-:## , right verb /:~ , and dyad { . This takes some practice to see, but there is an easier way, let J show you the structure by typing it into the ijx window and pressing enter:
<.#-:##{/:~
+---------------+-+------+
|+---------+-+-+|{|+--+-+|
||+--+-+--+|#|#|| ||/:|~||
|||<.|#|-:|| | || |+--+-+|
||+--+-+--+| | || | |
|+---------+-+-+| | |
+---------------+-+------+
Here you can see the structure of the verb (or, you will be able to after you get used to looking at these). Then, if you can't identify the pieces, play with them to see what they do.
10?20
15 10 18 7 17 12 19 16 4 2
/:~ 10?20
1 4 6 7 8 10 11 15 17 19
<.#-:## 10?20
5
You can break them down further and experiment as needed to figure them out (this little example is a median verb).
J packs a lot of code into a few characters and big tacit verbs can look very intimidating, even to experienced users. Experimenting will be quicker than your documenting method, and you can really learn a lot about J by trying to break down large complex verbs. I think I'd recommend focusing on trying to see the grammatical structure and then figure out the pieces, building it up step by step (since that's how you'll eventually be writing tacit verbs).
(I'm putting this in the answer section instead of editing the question because the question looks long enough as it is.)
I just found an excellent paper on the jsoftware website that works well in combination with Jordan's answer and the method I described in the question. The author makes some pertinent observations:
1) A verb modified by an adverb is a verb.
2) A train of more than three consecutive verbs is a series of forks, which may have a single verb or a hook at the far left-hand side depending on how many verbs there are.
This speeds up the process of translating a tacit expression into English, since it lets you group verbs and adverbs into conceptual units and then use the nested fork structure to quickly determine whether an instance of an operator is monadic or dyadic. Here's an example of a translation I did using the refined method:
d28=: [:+/\{.#],>:#[#(}.-}:)#]%>:#[
[: +/\
{.#] ,
>:#[ #
(}.-}:)#] %
>:#[
cap (plus infix prefix)
(head atop right argument) ravel
(increment atop left argument) tally
(behead minus curtail) atop right
argument
divided by
increment atop left argument
the partial sums of the sequence
defined by
the first item of the right argument,
raveled together with
(one plus the left argument) copies
of
(all but the first element) minus
(all but the last element)
of the right argument, divided by
(one plus the left argument).
the partial sums of the sequence
defined by
starting with the same initial point,
and appending consecutive copies of
points derived from the right argument by
subtracting each predecessor from its
successor
and dividing the result by the number
of copies to be made
Interpolating x-many values between
the items of y
I just want to talk about how I read:
<.#-:##{/:~
First off, I knew that if it was a function, from the command line, it had to be entered (for testing) as
(<.#-:##{/:~)
Now I looked at the stuff in the parenthesis. I saw a /:~, which returns a sorted list of its arguments, { which selects an item from a list, # which returns the number of items in a list, -: half, and <., floor...and I started to think that it might be median, - half of the number of items in the list rounded down, but how did # get its arguments? I looked at the # signs - and realized that there were three verbs there - so this is a fork. The list comes in at the right and is sorted, then at the left, the fork got the list to the # to get the number of arguments, and then we knew it took the floor of half of that. So now we have the execution sequence:
sort, and pass the output to the middle verb as the right argument.
Take the floor of half of the number of elements in the list, and that becomes the left argument of the middle verb.
Do the middle verb.
That is my approach. I agree that sometimes the phrases have too many odd things, and you need to look them up, but I am always figuring this stuff out at the J instant command line.
Personally, I think of J code in terms of what it does -- if I do not have any example arguments, I rapidly get lost. If I do have examples, it's usually easy for me to see what a sub-expression is doing.
And, when it gets hard, that means I need to look up a word in the dictionary, or possibly study its grammar.
Reading through the prescriptions here, I get the idea that this is not too different from how other people work with the language.
Maybe we should call this 'Test Driven Comprehension'?

Resources