APL At Its Core

July 2018

As it is the case with all groundbreaking languages, APL is not so much a language as it is a family of ideologically related sister languages. This family includes members like Dyalog APL, A+, K, Q, and of course the relatively popular J programming language.

A defining property of APL languages is their level of terseness and ability to crunch not just numbers, but also bulks of data with the very same piece of code. The problem with this definition however is that terseness is not an exclusive asset of the APL family of languages and neither is vector calculation. Extremely short code and the ability to manipulate arrays of data is also commonly attributed to the WolframLanguage, but I would not consider it an APL, because the way it gets their is a whole lot different.

Consider the following pieces of K3 and APL code to calculate the next generation in a game of life:

next:{|/(1;x)&3 4=\:+/,/-1 0 1!'\:/:-1 0 1!\:x}
next←{⊃∨/1 ⍵ ∧3 4=  +/, ¯1 0 1∘.⊖   ¯1 0 1⌽¨⊂⍵}

As you can see these code snippets roughly resemble themselves. The special thing about these lines of code is that they contain nothing special to game of life or cellular automatons of sorts. The algorithm only consists of rotation, equality comparison, arithmetic, boolean algebra, and functional reductions. The APL code also involves boxing and unboxing arrays as they are not ordinary scalar objects, which gives it kinda a Perlish touch, but other than that the code is just using very general programming language constructs also found in Python, various Lisp dialects, or really any other remotely high level language.

Implementing this algorithm in any of those languages would be incrementally more verbose though and the WolframLanguage would be no exception if not for the built-in CellularAutomaton function (yes, that's a thing in the preluded standard library). This of course feels a lot like cheating as having this functionality built-in offers no general advantage to people not doing anything cellular automaton related, whereas the APL primitives are generally applicable across domains.

It should be clear by now that fancy semantics is not what APL is about. There really is nothing special about the above K/APL code whatsoever semantics-wise. This code could very well be transcribed to Common Lisp, but guess what: it would absolutely suck, because it may not look like that to the uninitiated, but the above line of code does a lot, and I really mean a lot.

Just consider the extract !'\:/:. The ! operator means rotation in this context. If used as n!v it rotates the vector v by n. But we do not directly use the operator like that, we modify it using the adverb ', which generally denotes a functional map. Now we can do something like n!'m to rotate each row vector in the matrix m, that is each element of the vector of vectors m, by n, or more generally we can say something like v!'m to rotate each row m[i] in the matrix m by the corresponding element of v[i]. So whereas n!m would rotate the whole matrix, i.e. the columns, n!'m does a row rotation, but we did not stop there. We supply not one but three different rotations to perform (-1, 0, and 1), so we further amend the operator by applying the adverb map-over-left-argument (\:), which performs each of those three rotations separately. We also map over the right arguments (/:), as we actually get a vector of three different matrices supplied as the right argument.

So the six character excerpt !'\:/: would let alone be equivalent to something like (lambda (v m) (map (lambda (w) (map (lambda (x) (map (lambda (u) (rotate x u)) w)) v) m))) in a Lisp.

(As a little side note: People criticizing APL's readability tend to ignore how much a piece of APL code actually does. In proportion to that, I'd consider the K excerpt undeniably more readable than the mess this Lisp code is, even though it always fully spells out LAMBDA in plain English)

This does highlight that clever semantics are not the major strength of APL. To be fair, APL and J offer a great amount of moldability as they allow you to define your very own infix verbs, post-verbal adverbs, and infix conjunctions. This means you can do the following in J:

   4^2
16
   toThePowerOf =: ^
   4 toThePowerOf 2
16

Here we just "named" the power verb and use our own verb in an infix manner. This flexibility sadly makes true compilation very hard in the same way macros do, but it is not at all a defining property of the APL family.

K as an example does not have that flexibility: the user can very well define own functions, but not own verbs or adverbs that resemble those provided by the language. No one considers K to be the AutoLisp of APLs.

The real defining core of APLs is that they are not so much programming languages as they are programming notation. Take our three different maps as an example: it is totally reasonable for a programming language to provide a simple map function. Many Lisps do that. But APL is not so much about what is semantically provided as it is about what is syntactically feasible. This is why K not just provides the simple map adverb, it also provides syntax for the common case where you actually just want to map over one of the two arguments to a function. This is so common an idiom, that K provides it in the core language.

All of these reasons make it an absolute pleasure for me to program in APL dialects and especially in K as its evaluation model is highly influenced by Scheme (which gets its semantics right) and puts a layer of APL atop. Q is another more English like and regular syntax layer on top of K4, which I personally find nice for people learning the language, but then it is kinda too bloated at times, which makes it less appealing as a more regular K than say something like Klong.

Discuss