Tool of Thought

APL for the Practical Man

"We make software the old-fashioned way, we write it."

Whistling Past the Graveyard

April 30, 2026

The AI hype is at a fever pitch with tech CEOs declaring coding as we know it is done, over, kaput, a solved problem, while everyone from Uncle Bob to Linus Torvalds and DHH is embracing it, albeit in a measured way, with much less fervor than AI CEOs.

What's an APL programmer to think? We are, by definition, contrarians. After all, we use a language that's over 60 years old and fundamentally unchanged over that time. We use strange symbols. We don't write loops. Our opinion of AI will not follow the crowd.

Is there anything more absurd than using natural language to explain a complex problem to a computer? This is what APL is for. Why would we take a concise, powerful, unambiguous notation designed specifically for humans and not for computers, and replace it with a verbose, vague, error-prone means of communication between man and machine? This is one big step backwards. Who in their right mind wants to spend all day having a discussion and argument with a laptop about what to code? We can think of nothing worse than having to essentially talk to the computer for a living.

Perhaps AI is for programmers who use languages designed for computers rather than people.

Boilerplate

We often hear programmers lauding AI because it relieves them of the onerous task of writing boilerplate code. There are two problems here. First, if your language requires boilerplate, that itself is a problem. Second, it is one of the fundamental jobs of a programmer to eliminate duplication and boilerplate. What kind of programmer writes boilerplate code more than once? Not a good or experienced one.

Documentation

Another task that programmers hope to rid themselves of is writing documentation. If documentation is viewed as something to be tacked on to the end of a project after the design and programming is done, then documentation is indeed a chore, and largely a worthless effort too. Documentation is design. Nothing shows the flaws in a design more than explaining it on paper to the person who is going to use it. Documentation, like coding itself, is an essay, an attempt to understand a problem. Writing documentation as you code allows you to write what should be, rather than what is. It allows you to improve the design rather than accept it as given.

Tests

Programmers also hate writing tests, and hope AI will relieve them of this job too. But test are just documentation written in code rather than natural language. Tests perform the same function as documentation: design.

UI

Then there is the UI, yet another dreaded task of the lofty software engineer. But putting a button on a screen is not hard. Positioning it, making it a nice color, giving it a drop-shadow, or nice hover effect, none of this is hard. What is hard is deciding that the button should exist at all. Or, if it should exist, what screen should it be on. Or what other controls should be near it. Or what the button should do when clicked. The difficulty of UI is in the design, not the implementation.

Craftmanship

A hand-made software program is not like a pair of hand-made shoes. Hand-made shoes can be sold once and used by one person at a time. Hand-made software can be copied infinitely for zero cost. Software craftsmanship is not like physical world craftsmanship. Better tools never remove the need for software craftsmanship, and in fact do the opposite. Software is design, not manufacturing.

DIY

The AI promoters want everyone to write their own, disposable programs. This is silly. Some people want to build their own computers, or build their own guitars, or build their own cars, or their own APL interpreters. These people are valuable and more power to them. But the vast majority of people want to use a computer, a guitar or car that has been very carefully crafted by people deeply immersed in the specific problem. The same holds true for software. Even programmers want to use software carefully crafted by other programmers.

The Scam

The genius of the AI salesmen is that they have designed a software product that is bug-free by definition. If it is wrong, it just says Sorry! My mistake. You are absolutely right. Let's fix that! and then proceedes to get it wrong again. And again. And again. The AI companies want you to buy into this massive, expensive dependency just to write a line of code. AI for coding is a solution to a problem that shouldn't exist.

The Panels Component

January 14, 2026

The Abacus Panels component provides a way to layout content. We have two main goals for this component. First, we want to easily layout multiple components or elements with a minimum of fuss. For example, we may want to neatly place 3 components, one on the top and two below. Second, we may want to have a primary component (or two) that is always visible and then a side panel (or two, or three) that can be visible or invisble. Most UI frameworks that have a panels component allow only two panels, but each panel can of course be divided again into two panels. This is sort of the way splitters work in ⎕WC. We have taken a different approach.

We use CSS grid and one look at the grid-template-areas property should tell you how nice a fit it is for the new array notation in Dyalog v20.

For example:

     ...
     l←[
         0 0
         1 2
         3 3
     ]
     p←A.Panels.New ('Content' c) ('GridTemplateRows' l)        

There are four components or elements in the content c. This instructs Abacus to layout the first element across the entire parent component, the place the next two elements below that, in equal proportion, and finally the last component all the way across the bottom.

The values in l are simply indices into c, but we could use HTML ids as well. We can change the proportions of the components by changing l:

     l←[
         0 0 0 0
         1 1 1 2
         3 3 3 3
     ]

Consider a case of two components, a main component and a right-hand sidebar that should only be visible on demand. On startup the GridTemplateAreas is:

      [
        0
      ]

(or simply ⍪0) a 1 by 1 matrix. This indicates that the main component should take up all of the space. When the side bar is reqested we reset it to:

      [
        0 0 0 1
      ]

This makes the sidebar visible, and gives it 25% of the available space, shrinking the main component to 75% of the available space.

Panels can be resized, if desired and applicable, using keyboard shortcuts for now. At some point we will allow resizing via the mouse.

Variation on Iota

November 12, 2025

Several updates below!

We just encountered the following real-life problem, which oddly, if memory serves, we have never encountered before: Given a list of items, find the location of each item in a list of lists of items. An example will make it clear:

      a← 'abc' 'de' '' 'fgha'
      w←'hafxd'
      a {...} w
3 0 3 4 1

So, it's just like Index Of (), only we are looking a level deeper on the left for the existence of an item on the right. We are not concerned where in the sublist on the left the item on the right is found. It is simply the location of the sublist (where the item is found) within the main list.

Our first inclination is to flatten out a, and then lookup items in w in the new flat array:

      ⊃,/a
abcdefgha
      (⊃,/a)⍳w
7 0 5 9 3

Update: We should have noted that the items that we are dealing with might not be scaler, thus we must use ⊃,/ rather than simply .

Then we need to map these indices, which index into the flattened array, back to indices apropriate for the original nested array, which are given by:

        ⍳≢a
0 1 2 3

We can do this by counting the items in each sublist:

      ≢¨a
3 2 0 4

And then replicating:

      (≢¨a)/⍳≢a
0 0 0 1 1 3 3 3 3

Now we can map the first set of indices into the second set of indices to get the desired indices:

      (⊂(⊃,/a)⍳w)⌷(≢¨a)/⍳≢a
INDEX ERROR

Oops, we need one more index for things not found:

      (1,⍨≢¨a)/⍳1+≢a
0 0 0 1 1 3 3 3 3 4

And now, with a little code golf to remove a set of parentheses:

     (⊂w⍳⍨⊃,/a)⌷(1,⍨≢¨a)/⍳1+≢a
3 0 3 4 1

Bingo, we are done. Let's make it a dfn:

      liota←{(⊂⍵⍳⍨⊃,/⍺)⌷(1,⍨≢¨⍺)/⍳1+≢⍺}

Now let's try a completely different approach. Consider the outer product of membership ():

      w∘.∊a
0 0 0 1
1 0 0 1
0 0 0 1
0 0 0 0
0 1 0 0

If we look for the first occurance of a 1 in each row we get our answer:

      (↓w∘.∊a)⍳¨1
3 0 3 4 1

And a little golf:

      1⍳⍨¨↓w∘.∊a
3 0 3 4 1

For what it is worth, we can get rid of nesting the Boolean matrix and the each operator by using the rank operator , un-golfing in the process:

      1⍳⍨(⍤1)w∘.∊a
3 0 3 4 1

Maybe we can go tacit. It looks like we have a function between a and w, and then a function on the result, that is:

   g a f w

Which can be rewritten as an atop:

    a (g f) w

Where:

      f←∘.∊⍨
      g←⍳∘1⍤1

Let's see if it works:

      a (g f) w
3 0 3 4 1

Oh yeah! Now we can just combine into one function:

    liota2←⍳∘1⍤1∘.∊⍨ 
    a liota2 w
3 0 3 4 1

We can guess that while the tacit version is short and sweet, it's going to be a dog in terms of time and space due to the outer product when both arguments get large, and indeed the flat version is almost infinitely faster. That being said, in our use case, neither argument will ever be very large.

Update

Josh David provides a much nicer flat array solution using interval index that is both shorter and faster:

      {1+(+\≢¨⍺)⍸⍵⍳⍨⊃,/⍺}

It's amazing the various uses of .

Another Update

Aaron Hsu writes in with another solution using , but in its monadic form Where, which we had completely forgotten or maybe never knew can take an argument of non-negative integers, as well as the more typical Boolean:

      {((⍸≢¨⍺),≢⍺)[(∊⍺)⍳⍵]}

The Dyalog docs state that the model for Where can be expressed as {(,⍵)/,⍳⍴⍵}, which is exactly what we where doing in our first solution above.

We should have mentioned in the original post that our use case is not scaler integer or character items, but vectors of vectors representing names. So replacing with ⊃,/, and a little golf yields:

      {(⍸≢¨⍺,1)[⍵⍳⍨⊃,/⍺]} 

I was happy to discover that (⍸≢¨⍺),≢⍺ can be replaced by ⍳≢¨⍺,1.

Aaron also sent a tacit version (for scaler items only) with the new behind operator which is not even on my keyboard:

      (⊂∊⍛⍳)⌷((⍸≢¨),≢)⍤⊣

I wonder if this can be simplified a bit, given the golf above.

Yet Another Update

No discussion of should conclude without a thank you to the late Roger Hui. Thank you Roger!

We have a function, still in use, that is probably close to 40 years old, originally a trad fn, but converted to a dfn somewhere along the way:

ComputeRange←{                   ⍝ ⍺ breaks, ⍵ Values
     v←⍺,⍵                       ⍝ Gary Bergquist Page 70
     g←⍋v                        ⍝ Sort
     l←⍴⍺                        ⍝ First
     s←⍴⍵
     f←+\((l⍴1),s⍴0)[g]          ⍝ Slighly faster than:  f←+\((⍴v)↑l⍴1)[g]
     i←(⍴f)⍴0                    ⍝ Init
     i[g]←f                      ⍝ Index
     s⍴l↓i                       ⍝ Drop breaks points, enforce vec/scalar
 }

Note the reference to the legendary Gary Bergquist in the original comments, from whom we probably stole the entire function. This was used for one purpose only: grouping a numeric database column.

This is equivalent to the atop:

      1∘+⍸

It never would have occurred to us the many uses of this algorithm, and we never would have thought to apply this function in other situations.

Thank you Roger!

More posts...