Rigorous Nonsense - Readable Code is Unreadable
Rigorous Nonsense
Home<br>Archive
Readable Code is Unreadable
Posted on June 6, 2025
by Brandon Wilson
Tags: c, apl, j, whitney, software-architecture
J has an infamous prototype implementation called the J Incunabulum.<br>If you click that link, you’ll immediately understand why it has the reputation<br>it has.
I’d like to share why I now think this “unreadable” code actually communicates<br>better than so-called “readable” code.
Let me be clear; on my first encounter with the J Incunabulum, I found it<br>inscrutable. However, after having heavily steeped in APL for a few years, I<br>just gave it a quick re-read and had a completely different experience.
Seeing the Forest
The code catches flack for it’s single-letter names and terseness, but it<br>directly references bog standard APL and parsing concepts. What’s more<br>important is that the code macro-structure is immediately apparent and clearly<br>answers high-level questions up front.
What Are Our Basic Datatypes?
Right off the bat, Whitney tells us the kind of data we’ll be thinking about:
typedef char C;typedef long I;<br>typedef struct a{I t,r,d[3],p[2];}*A;<br>The first line gives us our main C types, and we can easily guess the second<br>defines an array structure. Indeed, at a bare minimum, APL arrays need to<br>encode type, rank, and shape metadata, telling us what t, r, and d are.<br>Thus p points to our element data. Note that d probably stands for<br>“dimension” or perhaps “depth”; I’m unsure on this point.
We also notice that d[3] says we only support up to rank 3, already signaling<br>the explicit limitations chosen for this proof of concept. It’s not entirely<br>clear why we have two p pointers, though. Let’s keep reading.
What Are Our Basic Operations?
Next we find the important fundamental operations we’ll be using to build our<br>new array language:
#define P printf<br>#define R return<br>#define V1(f) A f(w)A w;<br>#define V2(f) A f(a,w)A a,w;<br>#define DO(n,x) {I i=0,_n=(n);for(;it=t,z->r=r,mv(z->d,d,r);<br>R z;}<br>Apparently, we’ll be printing and returning a lot, as expected. APL functions<br>have fixed arity of one or two, calling the left argument ⍺ (alpha) and the<br>right ⍵ (omega). Considering that J functions are called “verbs”, it becomes<br>pretty clear that V1 and V2 are all the function prototypes needed for J<br>primitives. Note the K&R-style definitions.
DO defines our basic loop operation, so iterations will probably all<br>naïvely be O(1);
ma says we’ll be allocating 4-byte (i.e. 32-bit) chunks;
mv essentially gives us a copy operation over chunks;
tr or “times reduce” by inspection; and finally
ga generates a new array.
All off these—except for tr perhaps—are clearly going to be useful. The<br>32-bit and data model assumptions here are a bit dirty, but we can<br>forgive that in a proof of concept.
How About Using the Basic Operations?
Now that we know our basic toolkit here’s the implementation core!
V1(iota){I n=*w->p;A z=ga(0,1,&n);DO(n,z->p[i]=i);R z;}<br>V2(plus){I r=w->r,*d=w->d,n=tr(r,d);A z=ga(0,r,d);<br>DO(n,z->p[i]=a->p[i]+w->p[i]);R z;}<br>V2(from){I r=w->r-1,*d=w->d+1,n=tr(r,d);<br>A z=ga(w->t,r,d);mv(z->p,w->p+(n**a->p),n);R z;}<br>V1(box){A z=ga(1,0,0);*z->p=(I)w;R z;}<br>V2(cat){I an=tr(a->r,a->d),wn=tr(w->r,w->d),n=an+wn;<br>A z=ga(w->t,1,&n);mv(z->p,a->p,an);mv(z->p+an,w->p,wn);R z;}<br>V2(find){}<br>V2(rsh){I r=a->r?*a->d:1,n=tr(r,a->p),wn=tr(w->r,w->d);<br>A z=ga(w->t,r,a->p);mv(z->p,w->p,wn=n>wn?wn:n);<br>if(n-=wn)mv(z->p+wn,z->p,n);R z;}<br>V1(sha){A z=ga(0,1,&w->r);mv(z->p,w->d,w->r);R z;}<br>V1(id){R w;}V1(size){A z=ga(0,0,0);*z->p=w->r?*w->d:1;R z;}<br>All our basic functions, both dyadic and monadic, sit together in a single<br>block. In fact, each of these definitions is brutally simple and explicitly<br>elide array language features in service of architectural clarity. For example,
V2(plus){I r=w->r,*d=w->d,n=tr(r,d);A z=ga(0,r,d);<br>DO(n,z->p[i]=a->p[i]+w->p[i]);R z;}<br>Even if you don’t know APL, one can guess what “plus” should do. We already<br>know that V2 defines a new arity-2 function, and what’s the obvious way to<br>add arrays? Just vector addition, i.e. element-by-element, right?<br>Procedurally, we need to allocate an output array of the right size and then<br>populate it with the result, one element at a time.
Now we see why we want the tr helper: it gives us the data element count of<br>an arbitrary multi-dimensional array.
Also, as an APLer we immediately notice the elision of scalar extension.<br>That can me modeled with rsh on the left argument, so we’re probably just<br>defining some minimal subset of the primitive APL functions.
Other definitions all follow a similar pattern: 1) calculate the metadata for<br>the result array, 2) allocate a result in the conventional name z, and 3)<br>populate said array.
Notice, too, that find is not yet implemented. Curious. What else stands out?
V2(rsh){I r=a->r?*a->d:1,n=tr(r,a->p),wn=tr(w->r,w->d);<br>A z=ga(w->t,r,a->p);mv(z->p,w->p,wn=n>wn?wn:n);<br>if(n-=wn)mv(z->p+wn,z->p,n);R z;}<br>...<br>V1(id){R w;}V1(size){A...