Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Suggestivity and Idioms in APL (sacrideo.us)
74 points by mr_tyzik on May 8, 2023 | hide | past | favorite | 22 comments


For anyone looking to get into array programming, I'd recommend https://mlochbaum.github.io/BQN/. I'm no expert but I had a lot of fun using it for Advent of Code last year. I found it to be a lot more sensible and modern feeling than J (the only other one I've tried).


I walked in, realized just how advanced this was and then proceeded to read through the same way I pan through the Abstract Art section of the museum.

I freaking love APL and would love to learn it, but it is so incredibly jarring not knowing anything about the syntax.


I have a video series that covers every glyph in Dyalog APL, if you're interested in learning APL syntax:

https://www.youtube.com/playlist?list=PLfYUBJiXbdtSgU6S_3l6p...

There's also a concise guide covering every glyph, which if read in order only ever uses glyphs in examples that have been covered previously:

https://fastai.github.io/apl-study/

Finally, we have a discussion forum for learners:

https://forums.fast.ai/t/apl-array-programming/97188


I'd be happy to teach you; just let me know.

The syntax is in fact quite simple, and the semantics of the core language is mnemonic and limited, so I believe that a reasonably intelligent person can learn the entire core lanuage in 24 hours of dedicated studies. Subtract a few if the student has a knack for mathematics, but add a few if they have a CS degree.


I share your sentiments and it is incredible. To an outsider this might as well be a parody of programming blog posts. Such impenetrable syntax taken in stride:

    We can improve this approach by recognizing that we can handle the edge cases by using a catenation:
        ⍸0 1⍷0,⍵
        ⍸1 0⍷⍵,0
    Notice immediately how much better this feels.
Ah yes, how immediate is the feeling, much better, indeed. APL is so ridiculous, you have to love it.


As for the semantics, here are the equivalent JS functions:

    I =    y  => [...y.keys()].filter((e,i) => y[i])            // ⍸y Indices of trues in y
    E = (x,y) => y.map((e,i) => x.every((e,j) => e == y[i+j]))  // x⍷y mask indicating indices where x Exists as a sub-array in y
    C = (x,y) => [x,y].flat()                                   // x,y Catenate x and y into a single array
    
    w = [1,1,1,1,0,1,1,1,0,1,0,1,1,0,0,0,1,1,1,1]
    I(E([0,1],C(0,w)))  // [0,5,9,11,16]
Now, if I, E, and C were prefix/infix JS operators, with x being the left argument (if any) and y being the right argument, we'd write:

    I([0,1] E (0 C w))
All APL operators have long right scope, so we don't need to parenthesise right arguments:

    I [0,1] E 0 C w  // same syntax as APL's ⍸ 0 1 ⍷ 0 , ⍵
Alternatively, you can see the infix operators as being methods of all types:

   I([0,1].E(0.C(w)))
Now we remove all the .() noise:

   I [0,1] E 0 C w  // same syntax as APL's ⍸ 0 1 ⍷ 0 , ⍵
Wasn't that hard, was it?


The syntax is actually quite simple, but you may be confused by the unfamiliar symbols and lack of syntax. 0 1 is simply [0,1] in JSON and ⍵ is the argument name. The other symbols cirrespond to prefix and infix operators. Compare the following JS expression which is syntactically (but not semantically) equivalent to the APL expression below it:

    - [0,1] * 0 ** w
    ⍸  0 1  ⍷ 0 , ⍵


If you are interested in learning APL, take a look at https://xpqz.github.io/learnapl/intro.html

Disclaimer: author


Not knowing APL, I unfortunately find the beginning code completely unreadable:

  ⍸(⊃⍵)@0⊣¯1⌽0 1⍷⍵
[Edit: My thought process here is: "This syntax is so difficult that I can't even begin to reason about it even using web search. Can GPT understand it? Yep, it seems to."

"Could this algorithm be more clearly expressed in another language? Yes, it looks like it."]

----

I asked ChatGPT (4) to explain this expression (Prompt: "Explain what this APL code does: ___"). In case others find it helpful, the response is below.

First, let's break down each symbol:

1. ⍵ represents the right argument of a function.

2. ⊃ is the disclose or first function, which picks the first item of its argument.

3. ⍷ is the "where" function, which returns a boolean mask of where its left argument appears in its right argument.

4. ⌽ is the reverse function. With the argument ¯1, it rotates its argument one place to the right.

5. @ is the "at" function, which modifies certain elements of an array. (⊃⍵)@0⊣ replaces zeros in its right argument with the first element of ⍵.

6. ⊣ is the left function, which returns the left argument, ignoring the right one.

7. ⍸ is the "where" function, which returns the indices where its argument is true (or in the case of numeric data, non-zero).

Now, let's combine these to understand the whole expression:

0 1⍷⍵ returns a boolean mask of where the sequence 0 1 appears in ⍵.

¯1⌽ then rotates this mask one place to the right.

(⊃⍵)@0⊣ replaces zeros in the rotated mask with the first element of ⍵.

Finally, ⍸ returns the indices of non-zero elements in the result.

So, this code is finding the indices in the array ⍵ where a 0 is immediately followed by a 1, and replaces these 0s with the first element of ⍵. It then returns the indices of the non-zero elements in the result.

[Edit: I'm not entirely sure but this explanation might contain an off-by-one error. I don't quite follow APL enough to tell. The translated alternative does for sure though.]

----

On that note, I'll remark that I find APL's syntax to have distinct disadvantages. I know a number of programming languages well, having written production code in them for decades. I've come across basically every mainstream programming language and find them generally readable, even though I don't have much domain knowledge, because they share so many concepts that are typically reflected with similar syntax (for loops, assignments, functions, etc.) Even relatively more "out there" languages like Haskell and Perl are somewhat readable without a ton of domain knowledge.

I find it very difficult to get on board with languages that value terseness of this sort over readability (like in K and APL – especially with APL's special symbols). Code is read so much more than it's written, in general, that transparency is valuable and important – as well as the ability to search for code fragments to understand their meaning.

I asked ChatGPT4 to translate this code into Rust, for example, to see what it would come up with. Here was its literal translation, that relies on mutating the input (analogous to the APL code). This is similar to what you'd get transliterating the code into C/C++/Java:

  fn find_zero_one_and_replace(input: &mut Vec<i32>) -> Vec<usize> {
      let first_element = *input.first().unwrap_or(&0);
      let mut indices = vec![];
      let mut prev_zero_index = None;
  
      for (i, &item) in input.iter().enumerate() {
          if item == 0 {
              prev_zero_index = Some(i);
          } else if item == 1 {
              if let Some(index) = prev_zero_index {
                  input[index] = first_element;
                  indices.push(index);
                  prev_zero_index = None;
              }
          }
      }
      
      indices
  }
[Edit: I haven't checked whether this is correct]

After some back-and-forth, asking it to write a function that doesn't mutate the input, and uses iterators, it comes up with some code that I think is probably readable even if you don't know the language well (barring some particulars of the syntax):

  fn find_zero_one_indices<'a>(input: &'a [i32]) -> impl 
  Iterator<Item = usize> + 'a {
      input.windows(2)
          .enumerate()
          .filter_map(move |(i, window)| {
              if window == &[0, 1] {
                  Some(i+1)
              } else {
                  None
              }
          })
  }
[Edit: This code fails to detect the leading group of `1s` in the input and return a response beginning with `0`. Corrected code might be:]

  fn find_zero_one_indices<'a>(input: &'a [i32]) -> impl Iterator<Item = usize> + 'a {
      let mut previous = 0;
      input.iter().enumerate().filter_map(move |(i, &x)| {
          let result = if previous == 0 && x == 1 { Some(i) } else { None };
          previous = x;
          result
      })
  }
[Edit: The corrected form is arguably less readable than the original due to the need to detect a leading window that doesn't start with `0`. This version somewhat weakens my argument about the clarity of the alternative, but this is still readable. Perhaps someone else with more Rust experience can suggest a simplification.]

I find this readable since "windows" here is a direct reference to the concept of windowing, which is collecting sequences of items from a stream into groups. "Enumerate" iterates over a pair of sequence elements and their indexes: `(i, window)`. These concepts exist in a number of languages.

But if you didn't know what these code elements meant, you could search for them: "rust windows", "rust enumerate", "rust filter_map" and get useful results. The stdlib documentation will be the first or second link. For example, if you don't know what "filter_map" does: https://doc.rust-lang.org/std/iter/trait.Iterator.html#metho... (And the same would be true for Java, Python, etc.)

Each of these elements (windows, enumerate, filter) is syntactically joined into a conceptual pipeline, providing a framework for reasoning about how they work, in a paradigm used by many languages.

Finally we have the detection logic: `if window == &[0, 1]`. Hopefully that's readable, and I expect makes this solution easier to understand than any of the other variants.

I may have somewhat missed the point of the article: it goes on to describe how the solution can be generalized in various ways. But this is also likely true of the canonical solutions in many languages: iterators (in particular), anonymous functions, etc. enable code snippets to compose and generalize in many ways as well.


Why has this thing of posting unchecked GPT4 output become popular? It's infuriating. The explanation is wrong, but who cares right?

Oh great I see now they've added yet another APL-on-HN trope: the 'equivalent' (note: it's wrong too!) code in some trendy language (and saying APL/K are unreadable). It's like someone crafted the perfect message to piss me off.


I checked it insofar as I understand APL and what the algorithm was attempting to do: that the algorithm was attempting to detect and return the index of the first element of every consecutive "[0 1]" pair.

[Edit: OK, I see the mistake is that it should return the index of the `1`, not the `0`. Yes, that's the kind of mistake I'd catch during unit testing, or after more carefully reviewing the code.]

In what way is its explanation wrong? Its summary of the algorithm is:

> So, this code is finding the indices in the array ⍵ where a 0 is immediately followed by a 1, and replaces these 0s with the first element of ⍵. It then returns the indices of the non-zero elements in the result.

That summary seemed to correctly describe the algorithm, and seemed consistent with the article, so I assumed that its breakdown was correct. I don't understand the individual symbols or operators of APL and can't quickly fact-check the answer, but given that the summary was consistent with the article, it seemed to indicate that GPT had deduced (with no context from the article) what the algorithm was doing in human terms – in other words, whether it was able to "read" the APL code.

Whether it's actually mutating the input or not, I don't know. Its description of the transformation seemed to check out, though.

Regardless of whether its breakdown is right or wrong, my true intended commentary is about the opacity of the syntax and the difficulty in reading it (which I elaborated on via an edit, after your comment was posted but before seeing it).


> can't quickly fact-check the answer

If you would like to, visit https://tryapl.org/ and enter:

      ⎕IO ← 0
      bools ← 1 1 1 1 0 1 1 1 0 1 0 1 1 0 0 0 1 1 1 1
      {⍸(⊃⍵)@0⊣¯1⌽0 1⍷⍵} bools
The website will only(I think) let you copy-paste line by line, or it has the symbols in a bar along the top you can click on to enter them, and it has a backtick prefix system for typing them, so `L will enter the quad square on the first line and `[ enters the left arrow. Left arrow is variable assignment and the variable with a quad is a system one. The top line sets array indices count from zero instead of the default indexing from one. The curly braces {} make the code into a scriptblock/anonymous function which is immediately executed on the argument "bools".

If you want to see it building up stage by stage it works from the right, these give the intermediate steps:

    {0 1⍷⍵} bools

    {¯1⌽0 1⍷⍵} bools

    {(⊃⍵)@0⊣¯1⌽0 1⍷⍵} bools

    {⍸(⊃⍵)@0⊣¯1⌽0 1⍷⍵} bools
> "Regardless of whether its breakdown is right or wrong, my true intended commentary is about the opacity of the syntax and the difficulty in reading it (which I elaborated on via an edit, after your comment was posted but before seeing it)."

Which is a bit of a shame because the point of the article is at the end: "I do not believe that you can leverage this kind of suggestivity with languages that are more verbose. [...] It is fundamentally easier to try out many single line expressions than it is to try out many 15 - 30 line expressions. [...] in the same way that any technique that reduces the length of the feedback cycle improves our ability to iterate and therefore learn.".

It's not saying that APL is easy to read, it's claiming that learning to read it/think in it has benefits that don't apply to longer languages - more forest less trees, more design less bricklaying. Taking that away into "I never learned it and it looks hard" is not going with the more interesting part of the claim - like, would you rewrite your Rust code to detect the end of groups, then compare both versions and see they are annoyingly clunky, then search for a version which is more pleasing and suggests variations which may do other useful or interesting things with the groups? Even if you did, doing so may be harder because the Rust code is so much longer (more effort to compare in your head) and you would be less able to because the Rust code took more time to write so you have less time left over.

[This is rather more for the world of doing a mathematical puzzle, seeing two equations look similar and realising that the puzzles share some common underlying thing which you hadn't noticed before, than the world of writing production webservers with fearless concurrency].


<mean message edited out - despite me really wanting to be mean to someone who's been programming for longer than I've been alive but seems to lack reading comprehension>

You don't have to understand APL to know what it's attempting. From the article it clearly is meant to return [0,5,9,11,16], your rust returns [4, 8, 10, 15] (at least the second one, the first one doesn't even compile!).


> From the article it clearly is meant to return [0,5,9,11,16], your rust returns [4, 8, 10, 15].

You're right, I didn't test the code, nor review it especially closely.

I took a quick glance for a couple of seconds, checked that it compiled, glanced at the primitives it was using, and said "LGTM". I wanted to know "What general shape will this algorithm take expressed a different way?"

You shouldn't expect perfection from someone of any skill level posting or discussing topics casually like this. The amount of time and effort I'm willing to spend on comments is limited – limited enough to allow an off-by-one error to slip through. This is commentary in an Internet forum, not a code review for a production system, or a formal publication.

However, I think the nature of the error and its fix actually reinforces my point. If one did review the Rust code closely (which I didn't – I was more interested in GPT's capability to explain the APL syntax; and what the algorithm would look like expressed in another language), then a mistake would have stood out:

  .filter_map(move |(i, window)| {
    if window == &[0, 1] {
      Some(i)
If we want to return the index of the `1`, not the `0`, then `i` is the wrong index to return. I assume that's the mistake you're pointing out.

Yes, it's true that I didn't even realize that the original algorithm was returning the second index of these elements and not the first. I barely skimmed those details before becoming entirely distracted by APL's syntax itself, and then started to wonder about the various other ways that such an algorithm might be implemented in another language (and how clearly, subjectively). "The APL code is opaque. Will alternatives be opaque?"

Yes, a mistake like this would be trivially caught upon a close review of the algorithm or code, or on any unit testing.

However, I think it bolsters my point that the correction to the Rust code is easy to understand – both the mistake and the nature of the fix:

  .filter_map(move |(i, window)| {
    if window == &[0, 1] {
      Some(i + 1)
(The only thing that's not immediately obvious is why `i+1` is guaranteed to be a valid index – which it is.)

[Edit: Now that I've had time to actually run and test the code, a second error is that it omits the first group of `1`s. A corrected version is:

  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()
  }
This returns `Some(i)` again rather than `Some(i+1)` because we've prepended a `0` to the input.]

My comment is not about Rust specifically. It's just the language I chose. I assume the translation would be roughly similar in Java (with iterators), Python, Ruby, and the imperative version in C and C++ and similar.

And I believe in most of these languages, the mistake and its correction would both be easy to see and understand with close enough review. I'm not certain that would be true for the APL code. If you gave me that algorithm in any mainstream language, and asked me to look for mistakes, I could probably spot that problem upon reviewing it, and probably know how to fix it. (Probably, not necessarily.)

I do not believe that I could spot an analogous problem in the APL code nor understand easily how to fix it. My overall point is that the APL code seems entirely unapproachable to the un-initiated – even for a programmer experienced with numerous other languages.

(I might have similar difficulty with Haskell, but probably not as much; and perhaps Scala if it was written in a particularly obscure way.)

----

An additional topic that I would be interested to explore is: what kind of errors would be likely in the APL code? What would the APL code look like that had a similar off-by-one error? Would it be easy to spot by reading the code? And what would the "diff" look like correcting it? I unfortunately don't know enough about APL to explore this.

----

[In response to several edits of your comment that contained ad hominem attacks, and a lack of explanation of your criticism:]

Please follow the Hacker News guidelines:

https://news.ycombinator.com/newsguidelines.html

> Be kind. Don't be snarky. Converse curiously; don't cross-examine. Edit out swipes.

> Comments should get more thoughtful and substantive, not less, as a topic gets more divisive.

> When disagreeing, please reply to the argument instead of calling names. "That is idiotic; 1 + 1 is 2, not 3" can be shortened to "1 + 1 is 2, not 3."


> “What would the APL code look like that had a similar off-by-one error? Would it be easy to spot by reading the code?

It kinda does have an off by one error; (Dyalog) APL defaults to indexing from one and the code assumes indexing from zero. The blog author isa well known APL-er and other “modern APL” users often assume indexing from zero. And the change/fix is the quad-io in my other comment so it’s not easily visible in the code because it’s not in the code at all but it is an easy fix.

At least, that’s one kind of off-by-one error; APL doing whole array transforms in each operation tends not to have as much room for off-by-one errors when you aren’t counting through things in the typical imperative way.


[flagged]


I'm afraid your comments are unfortunately still well on the wrong side of the HN guidelines. In your GP comment, "someone who's been programming for longer than I've been alive but seems to lack reading comprehension" is a swipe, and the comment I'm replying to here is way too aggressive. "I expect you to test" is supercilious, "complete nonsense" is name-calling, "I don't believe you could fix it in APL [...] but then again you can't seem to fix it in any other language either" is a personal putdown, as is "Clearly rust is too as you posted rust code that doesn't work".

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.


Perhaps you should give some thought to discouraging the use of ChatGPT as if it were a source of information. The tendency to use it as a substitute for understanding or research seems corrosive to discourse.


Auto-generated comments are banned on HN but I think we'd get accused of overreach if we tried to tell people how they should use ChatGPT.


Barging into a conversation with what amounts to "I don't know what I'm talking about, but I expect you to read the next five paragraphs of GPT copy-pasta" is at least as harmful as a "shallow dismissal" or "internet trope". It's a gish-gallop at best, and outright spam at worst. It is not overreach to curb antisocial behavior, whether machine-assisted or naturally-occurring.


I agree! (except I think "antisocial behavior" in the last sentence is too harsh). But I don't see how we can make a moderation rule out of it. "Don't quote ChatGPT as if it were an authoritative source of information" is much too specific. We'd need some sort of general principle.


Now that I've had the opportunity to actually run the code and test it against the input from the article, here's a corrected version:

  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.)


I believe the point of the article can be understandable without understanding the way the code works at all and largely without understanding APL syntax - it was not about comparing the algorithms, but about how the related solutions have code which looks related which then affects how you explore the problem space. (The problem space being: an array of bools where boolean trues are grouped together, starting with finding where those groups begin and end). Here's another attempt to restate the article: without any detail about how the code actually functions to achieve the tasks I just tell you that these two lines of code do related things:

    ⍸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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: