Tacit programming is usually faster and more efficient, because you can tell J exactly what you want to do, instead of making it find out as it goes along your sentence. But as someone loving the hell out of tacit programming, I can also say that tacit programming encourages you to think about things in the J way.
To spoil the ending and answer your question: yes, tacit programming can and does convey information about structure. Technically, it emphasizes meaning above all else, but many of the operators that feature prominently in the less-trivial expressions you'll encounter (@: & &. ^:
to name a few) have very structure-related meanings.
The canonical example of why it pays to write tacit code is the special code for modular exponentiation, along with the assurance that there are many more shortcuts like it:
ts =: 6!:2, 7!:2@] NB. time and space
100 ts '2 (1e6&| @ ^) 8888x'
2.3356e_5 16640
100 ts '1e6 | 2 ^ 8888x'
0.00787232 8.496e6
The other major thing you'll hear said is that when J sees an explicit definition, it has to parse and eval it every single time it applies it:
NB. use rank 0 to apply the verb a large number of times
100 ts 'i (4 : ''x + y + x * y'')"0 i=.i.100 100' NB. naive
0.0136254 404096
100 ts 'i (+ + *)"0 i=.i.100 100' NB. tacit
0.00271868 265728
NB. J is spending the time difference reinterpreting the definition each time
100 ts 'i (4 : ''x (+ + *) y'')"0 i=.i.100 100'
0.0136336 273024
But both of these reasons take a backseat to the idea that J has a very distinct style of solving problems. There is no if, there is ^:
. There is no looping, there is rank. Likewise, Ken saw beauty in the fact that in calculus, f+g was the pointwise sum of functions—indeed, one defines f+g to be the function where (f+g)(x) = f(x) + g(x)—and since J was already so good at pointwise array addition, why stop there?
Just as a language like Haskell revels in the pleasure of combining higher-order functions together instead of "manually" syncing them up end to end, so does J. Semantically, take a look at the following examples:
h =: 3 : '(f y) + g y'
– h
is a function that grabs its argument y
, plugs it into f
and g
, and funnels the results into a sum.
h =: f + g
– h
is the sum of the functions f
and g
.
(A < B) +. (A = B)
– "A is less than B or A is equal to B."
A (< +. =) B
– "A is less than or equal to B."
It's a lot more algebraic. And I've only talked about trains thus far; there's a lot to be said about the handiness of tools like ^:
or &.
. The lesson is fairly clear, though: J wants it to be easy to talk about your functions algebraically. If you had to wrap all your actions in a 3 :''
or 4 :''
—or worse, name them on a separate line!—every time you wanted to apply them interestingly (like via /
or ^:
or ;.
) you'd probably be very turned off from J.
Sure, I admit you will be hard-pressed to find examples as elegant as these as your expressions get more complex. The tacit style just takes some getting used to. The vocab has to be familiar (if not second nature) to you, and even then sometimes you have the pleasure of slogging through code that is simply inexcusable. This can happen with any language.