I happen personally to agree with you about shallow dismissals of APL and the other array languages and have felt rather strongly about it at times as well, so I completely get where you're coming from. But that makes it painful for someone like me to read nasty posts flaming people for getting it wrong (or what you and I would call wrong). All that achieves is to alienate not only the person you're talking to but also any neutral readers who don't know much about the subject—and that is the vast majority of readers. It not only doesn't help your (our) position, it gives people a new reason to have negative associations with these little-understood languages, which are much too beautiful and powerful to deserve that.
You clearly know a lot about this topic and you're more than welcome to share what you know on HN. One function of this site is to be an educational hub for non-mainstream knowledge about computing (and other topics as well, but especially computing). However, we need you to do so within the site guidelines (https://news.ycombinator.com/newsguidelines.html) which includes being respectful and kind to people who know less than you.
It's great that you edited out the worst personal attack and the fact that you did that makes it clear that you have good intentions—which I appreciate. But it isn't enough to leave posts that are full of putdowns. We really can't bend these rules, nor is it in your interest for us to bend them because, as I said, all they do is make your minority position even more of a minority position, and that hurts you yourself as well as the rest of us who love these languages.
I definitely hope you'll keep posting and sharing interesting things about APL, but if you'd please review https://news.ycombinator.com/newsguidelines.html and take the intended spirit of the site more to heart, we'd be grateful.
fn find_zero_one_indices<I>(input: I) -> Vec<usize>
where
I: IntoIterator<Item = i32>,
{
let input: Vec<_> = std::iter::once(0).chain(input.into_iter()).collect();
input.windows(2)
.enumerate()
.filter_map(|(i, window)| {
if window == &[0, 1] {
Some(i)
} else {
None
}
})
.collect()
}
There's a limit to how much time I'm willing to spend on this, which I see as tangential to the point I was attempting to explore in my original comment, which was about how the opacity of the APL syntax made it difficult to engage with the ideas; and how using an alternative syntax might make it easier to engage in a discussion about algorithms.(Yes, I realize the article comments on this. Still, it's difficult to engage with those meta-ideas because the article's analysis relies heavily on APL syntax and constructs.)
To really understand the point being made by the article, I'd need to follow it step-by-step, and attempt to translate each code fragment it's discussing into something comparable in another language; and then attempt to see how those fragments or functions compose, or fail to compose, in the same way that they can in APL. I'd need to see a side-by-side comparison.
True, the Rust code above is a lot longer than "⍸(⊃⍵)@0⊣¯1⌽0 1⍷⍵", but I question to what degree it's less modular. The constructs that the Rust code relies on, like `chain`, `windows`, `enumerate`, `flat_map`, etc., are (as far as I understand) similar, general-purpose constructs analogous to APL operators (or whatever the right term is). They can even operate over higher-rank spaces in conjunction with suitable libraries.
So it's not immediately obvious to me that the APL code fragments discussed in the article do not have analogous code fragments in languages like Rust. Perhaps this dimension of higher-order programming (rank-agnostic code? would that be a correct description?) is most useful in numerical computation, which is not my forte.
Or perhaps it's gone over my head relative to the time that I've invested in it, and to understand the ideas I'd need to read it more carefully, like an academic paper.
(Remark: Insert "your favorite language here" in place of Rust. I chose Rust in my example simply because I'm learning it; not in an attempt to proclaim it as superior. I believe Java and Python and most other modern languages could probably express the algorithm similarly.)
⍸0 1⍷0,⍵
⍸1 0⍷⍵,0
You can see that from the first line to the second line, on the left the 0 1 swaps around to 1 0 and on the right the 0,⍵ swaps around to ⍵,0 but the overall shape/size/length/structure/symbols are the same between the two lines. That is, I state that they do something conceptually similar and you can see they look visually similar; they do something with the concept (start, end) swapped and visually have something swapped around. Next, looking at these lines might make you wonder whether changing the digits on the left side to 0 0 or 1 1 would do anything related and interesting with groups of bools? Or whether changing the digit that goes with ⍵ to a 1 would do anything interesting in the problem space? The visual look of the code suggests these possibilities - they were changed from one line to the next, and you could continue the changes while keeping the structure. The shortness of the code makes it very easy to mentally compare the lines and very quick and low effort to try the changes and find out.By contrast, these two lines do the same pair of tasks as the above lines:
⍸(⊃⍵)@0⊣¯1⌽0 1⍷⍵
⍸(⊃⌽⍵)@(¯1+≢⍵)⊢1 0⍷⍵
They are visually far more distinct from each other, structurally more complex with parentheses; not comparing with the previous pair, just between these two lines there are different symbols and different lengths and new sections being inserted. It's no longer visually so clear that they do related things. Looking at them there is no strong suggestion how you might continue similar changes to find a third or fourth pattern which may do something interesting with groups of bools. And the code is more complex so you are already spending more of your finite mental effort thinking about how the code works, rather than exploring the problem domain. Now compared to the previous pair they are longer, less elegant.From your translation to Rust you have moved away from the high level view of groups of bools as soon as you name the function "find_zero_one_indices" as if the zero and one are the significant things you care about instead of incidental implementation details, one way to find the groups; already with that name there's no tempting reason to rewrite it to find the "one_zero" indices (you might name the function "startingIndicesOfGroups" then look in the code to wonder how to find the ending ones?). And if you do, once you have written it again for the one_zero indices and you've got 32 lines of code, ~500 characters with ~158 symbols, how visually clear is it at a glance what changed to match the start and end? And are you going to bother writing a "find_zero_zero_indices" from a moment of curiosity to see whether that makes an interesting group pattern when it takes another 16 lines? Are you going to be bothered to copy half a dozen variations? Once your head is full of Vec<usize> and chain(.into_iter()).collect() have you lost sight of the group start/end altogether while you wonder if any of your code will trip the borrow checker?
The article then moves on to this pair:
⍸2<⌿0⍪⍵
⍸2>⌿⍵⍪0
Where you can see that on the left the less-than changes to greater-than, and 0⍪⍵ swaps around to ⍵⍪0. These quickly suggest that you could change the 0 to 1 and see what happens, or change the <> to any other boolean comparison like ≤ ≥ or operation (AND,OR,NOT) and it's only ten characters to try them out and explore what they do. Compared to the first pair these are a similar length, but they have more things to change while keeping the overall structure and most of the symbols, which makes them more interesting and more generally useful structures.So the claim of the article is not about which one is more modular, or which one is a nicer algorithm, it's about which pairs make the similar work look similar, where the code is short enough that it's easy to compare in your head with little effort, and where the changes between them are a simple enough pattern that the code 'suggests' how you could continue the pattern of changes. And claims or implies that APL is particularly strong at this because of the tersness of the code.