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