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'?
Related
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
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.
This is a first run-in with not only bitwise ops in python, but also strange (to me) syntax.
for i in range(2**len(set_)//2):
parts = [set(), set()]
for item in set_:
parts[i&1].add(item)
i >>= 1
For context, set_ is just a list of 4 letters.
There's a bit to unpack here. First, I've never seen [set(), set()]. I must be using the wrong keywords, as I couldn't find it in the docs. It looks like it creates a matrix in pythontutor, but I cannot say for certain. Second, while parts[i&1] is a slicing operation, I'm not entirely sure why a bitwise operation is required. For example, 0&1 should be 1 and 1&1 should be 0 (carry the one), so binary 10 (or 2 in decimal)? Finally, the last bitwise operation is completely bewildering. I believe a right shift is the same as dividing by two (I hope), but why i>>=1? I don't know how to interpret that. Any guidance would be sincerely appreciated.
[set(), set()] creates a list consisting of two empty sets.
0&1 is 0, 1&1 is 1. There is no carry in bitwise operations. parts[i&1] therefore refers to the first set when i is even, the second when i is odd.
i >>= 1 shifts right by one bit (which is indeed the same as dividing by two), then assigns the result back to i. It's the same basic concept as using i += 1 to increment a variable.
The effect of the inner loop is to partition the elements of _set into two subsets, based on the bits of i. If the limit in the outer loop had been simply 2 ** len(_set), the code would generate every possible such partitioning. But since that limit was divided by two, only half of the possible partitions get generated - I couldn't guess what the point of that might be, without more context.
I've never seen [set(), set()]
This isn't anything interesting, just a list with two new sets in it. So you have seen it, because it's not new syntax. Just a list and constructors.
parts[i&1]
This tests the least significant bit of i and selects either parts[0] (if the lsb was 0) or parts[1] (if the lsb was 1). Nothing fancy like slicing, just plain old indexing into a list. The thing you get out is a set, .add(item) does the obvious thing: adds something to whichever set was selected.
but why i>>=1? I don't know how to interpret that
Take the bits in i and move them one position to the right, dropping the old lsb, and keeping the sign. Sort of like this
Except of course that in Python you have arbitrary-precision integers, so it's however long it needs to be instead of 8 bits.
For positive numbers, the part about copying the sign is irrelevant.
You can think of right shift by 1 as a flooring division by 2 (this is different from truncation, negative numbers are rounded towards negative infinity, eg -1 >> 1 = -1), but that interpretation is usually more complicated to reason about.
Anyway, the way it is used here is just a way to loop through the bits of i, testing them one by one from low to high, but instead of changing which bit it tests it moves the bit it wants to test into the same position every time.
I am aware that languages like Prolog allow you to write things like the following:
mortal(X) :- man(X). % All men are mortal
man(socrates). % Socrates is a man
?- mortal(socrates). % Is Socrates mortal?
yes
What I want is something like this, but backwards. Suppose I have this:
mortal(X) :- man(X).
man(socrates).
man(plato).
man(aristotle).
I then ask it to give me a random X for which mortal(X) is true (thus it should give me one of 'socrates', 'plato', or 'aristotle' according to some random seed).
My questions are:
Does this sort of reverse inference have a name?
Are there any languages or libraries that support it?
EDIT
As somebody below pointed out, you can simply ask mortal(X) and it will return all X, from which you can simply pick a random one from the list. What if, however, that list would be very large, perhaps in the billions? Obviously in that case it wouldn't do to generate every possible result before picking one.
To see how this would be a practical problem, imagine a simple grammar that generated a random sentence of the form "adjective1 noun1 adverb transitive_verb adjective2 noun2". If the lists of adjectives, nouns, verbs, etc. are very large, you can see how the combinatorial explosion is a problem. If each list had 1000 words, you'd have 1000^6 possible sentences.
Instead of the deep-first search of Prolog, a randomized deep-first search strategy could be easyly implemented. All that is required is to randomize the program flow at choice points so that every time a disjunction is reached a random pole on the search tree (= prolog program) is selected instead of the first.
Though, note that this approach does not guarantees that all the solutions will be equally probable. To guarantee that, it is required to known in advance how many solutions will be generated by every pole to weight the randomization accordingly.
I've never used Prolog or anything similar, but judging by what Wikipedia says on the subject, asking
?- mortal(X).
should list everything for which mortal is true. After that, just pick one of the results.
So to answer your questions,
I'd go with "a query with a variable in it"
From what I can tell, Prolog itself should support it quite fine.
I dont think that you can calculate the nth solution directly but you can calculate the n first solutions (n randomly picked) and pick the last. Of course this would be problematic if n=10^(big_number)...
You could also do something like
mortal(ID,X) :- man(ID,X).
man(X):- random(1,4,ID), man(ID,X).
man(1,socrates).
man(2,plato).
man(3,aristotle).
but the problem is that if not every man was mortal, for example if only 1 out of 1000000 was mortal you would have to search a lot. It would be like searching for solutions for an equation by trying random numbers till you find one.
You could develop some sort of heuristic to find a solution close to the number but that may affect (negatively) the randomness.
I suspect that there is no way to do it more efficiently: you either have to calculate the set of solutions and pick one or pick one member of the superset of all solutions till you find one solution. But don't take my word for it xd
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...