Decoding Inverses
Everyone has something to learn, and we should always be open to those new things the world around is showing us. I learned a "new" trick in APL recently from none other than an attendee of one of my APL workshops (thanks Aditya!). He is the first to admit that the discovery is hardly original to him, he having himself learned from an insightful Stack Overflow user, but in my mind, he is still the one to solidify things in my head, so he deserves the credit for teaching me.
I was, in fact, so shocked at not knowing about this beforehand that I thought maybe this was some sort of new feature, but Roger Hui assures me that it has existed pretty much from the beginning. I think the word "forever" was thrown about.
You may think I am gushing about nothing, but it is amazing how much a quality of life thing this feature is. In fact, it is one of those things I think many may fail to properly appreciate until they are forced to live without it.
And what fine magic elixer has so stolen my fancy that I must write about it? Why it is the simple but "why didn't I think of that?" fact that ⊥ is in the domain of ⍣¯1!
"What?" I hear you say. You are even now wondering what may make this particular combination worth so much trouble, to which I respond, no trouble at all!
The key resets in the design of ⊥ and ⊤. Specifically, they are not symmetric. We often think about them as inverses of one another, but that is really only true for the case when their left arguments are both greater in length than 1. When dealing with singleton or scalar left arguments, ⊥ will extend that scalar value to the tally of its right argument, thus fully decoding the information contained in the right argument. When dealing with ⊤ on the other hand, the tally of the result will never disagree with the length of the left argument. If you have a number that cannot be fully encoded using that number of places, then you end up with an often intentionally truncated result. This means you can never recover the original input with any form of ⊥.
This leads to some awkward formulations if you do not know the max range of your input a priori. Instead, you must compute the max of the input and then do some work with logarithms to figure out how many digits your encoding will require and thus the length of your left argument to ⊤. This may make an otherwise simple and obvious computation much less so. Consider a possible solution to dismal addition
db←10⊥(⌈/⊢⊤⍨10⍴⍨∘⌈1+10⍟⌈/)
However, instead of doing this compuation explicitly, you can have it all happen implicitly by using ⊥⍣¯1 instead of ⊤.
da←10⊥(⌈/10⊥⍣¯1⊢)
I always felt like this use case of ⊤ was very clumsy for the language and wondered why there was not a more direct way to handle stuff like this, but as it turns out, I just was not looking in the right places!
Remind me to never underestimate the power of inverses again.