I saw this recent tweet by Conor Hoekstra, which has some artistic code comparisons.
The problem he gives is to write a function that can tell whether there are at least 3 consecutive odd numbers in a given array.
He broadly compares three algorithmic solutions across a few different languages:
- what’s up with BQN partitioning?
- is APL’s scan really broken?
- (sorry q, perhaps I’ll learn about you another time.)
So, why is writing a “ChunkBy” algorithm harder in BQN than APL?
The main difference is in the following glyphs: APL has a dedicated ‘partition’ function (dyadic
⊆
), whereas BQN has the more generalised ‘group’
(⊔)
.
Group has a bit of a learning curve to it, but is the more flexible of the two primitives; the trade-off is that partitions in BQN have to be done idiomatically rather than as a single glyph.
Here is a potential BQN partition solution to Conor’s problem:
|
|
Or, alternatively:
|
|
These both look overly cumbersome, and well, they are.
However, I think there is a hidden clue as to why: within each partition function is a plus scan
(+`)
, and isn’t that already the basis for the first algorithm?
I would take that as a hint from the cosmos that partitioning is not the highly baconic approach here, and to instead go with either a scan or windowed reduction.
The second curiosity is the missing APL solution in the above image. Is APL’s ‘scan’ really broken? That sounds like a rather damning indictment.
Here is what a plus scan looks like:
|
|
Which seems fine at first, but once you start plugging in non-commutative functions e.g. subtraction it starts to get weirder.
This example from Mastering Dyalog APL highlights the problem:
|
|
Scan in APL is applied left-to-right, so why do we not instead get
(3 ¯3 ¯4 ¯12 ¯17)
?
The missing ingredient is that each intermediate is really a minus reduction
(-/)
of the elements up to n, and reductions in APL are evaluated right-to-left. Therein lies the problem!
For example, applying a minus reduction to the first four elements yields the fourth element of the minus scan:
|
|
So it works as intended, albeit with a slightly odd design.
I think that the perceived “brokenness” with APL scan is more that it doesn’t behave in line with other languages.
Take BQN for example:
|
|
BQN also scans left-to-right, but each intermediate is instead equivalent to a left fold:
|
|
Whilst this is slightly at odds with
´
being a right fold in BQN (left fold is
𝕗˜´⌽
),
I think that this is still the more intuitive scan primitive, so I do sympathise with Conor’s assertion.
As another example, Haskell also agrees with this left scan definition:
|
|
I had a go at unrolling the APL behaviour to create a left scan in line with Haskell and BQN, which resulted in the following mess:
|
|
This is taking the prefixes by doing a join scan
(,\)
and then composing a minus ’left’ reduction
(-⍨/∘⌽)
for each
(¨)
prefix (yes, I am trying to sound smart).
An APL ‘scan’ solution to Conor’s problem could look something like the following:
|
|
It works, but perhaps the cosmos are whispering something about APL’s scan here too.
(…iiitttsss bbbrrroookkkeeennn…)