Question

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.

Was it helpful?

Solution

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

OTHER TIPS

Perhaps not an overwhelmingly compelling application but,

 (4 # 256) #: 8234092340238420938420394820394820349820349820349x

is 10x faster than

 256 #. inv (2^32x) | 8234092340238420938420394820394820349820349820349x
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top