There are few things as humbling as learning a programming language of a radically different paradigm from any you’ve used before. Recently, I’ve been feeling a need to learn a language with which I could easily manipulate data – usually collections of numbers. I thought about learning R or Julia, but while I’m sure those systems are extremely useful, they didn’t seem to offer the chance to have my world rocked and my conception of reality turned upside down. As long as I’m going to learn a language for practical reasons, I might as well learn a Perlis language that will show me (once again) just how much I have to learn about software, so I decided to learn J.
Honestly, the fact that I’m learning J is still a little surprising to me. When I first heard about APL (the language J descends from, which defines the language category), it was mainly to the effect of: “it’s an old language that required a custom keyboard, LOL.” I thought of it as a historical relic, like RPG or COBOL, whose lessons had been thoroughly integrated into more modern languages and didn’t offer much reason to learn unless you needed them for a specific purpose.
Thanks to some friends at Atomic (Karlin and Scott) and some long bus rides with Tracy Harms, I’ve since learned the error of my ways. Not only is the family interesting, it stands a bit apart from other paradigms, pollenating mainstream languages less than the functional or logic paradigms, for example.
So now’s the time to do my penance. Here are a few of the reasons I’m already happy with my choice.
J is the most dynamically typed language I’ve ever used — not just in the technical sense, but in feel. Dynamically typed languages defer type checking to runtime, allowing you to try any operation you want in your code; it just might fail if the operation is attempted on an invalid value. Because of this, dynamically typed object-oriented languages like Ruby and Python have a fluidity to them compared to, say, Java. Objects are often “duck-typed”, in that as long as an object supports the methods a function is expecting, it can be passed to the function and everything will work. Objects are just values upon which to invoke method dispatch, and they all support dispatch in a uniform way — they just don’t all respond to the same methods.
This fluidity makes dynamic languages flexible and convenient, sometimes enabling code reuse in unexpected ways. But I don’t think Ruby and Python, at least, turn the freedom-and-flexibility dial of dynamic typing up to 11. Most OO systems exhibit the “every object is a beautiful and unique snowflake” problem. Though method invocation is uniform across objects, the different sets of methods supported by different objects often lead to cases where things that are alike just aren’t alike enough to enable reuse. This problem is what I think is at the heart of connascence of type, where type compatibility issues lead to duplication.
J turns this dial up to 11 through exceptional regularity of values and functions.
One way J achieves this is by doing away with the “beautiful and unique snowflake” narcissism that objects often exhibit. Most operations in J do something reasonable for most values. Functions are often total or at least well-defined for a large proportion of possible inputs. If a function does what you want in one case, you can almost always use it in another similar case, without having to worry about differences in the specifics.
This property ends up being extremely important in practice because it leads to massive composability. The composition of total functions is itself total, so the functions you build up in J are often as generally useful as the built-in functions, or at least nearly so. This and related choices seem to lead to a virtuous cycle, where smaller programs can be assembled into larger ones the way you want it to happen, without being defeated by small subtle instances of short-sightedness that lead to pieces not quite fitting together the way they need to.
The fundamental property (or at least the one I’ve seen so far) that enables J’s composability is the power of its choice of domain, and the regularity in how that domain is operated upon. J is all about manipulating numbers or n-dimensional arrays of numbers. (Sometimes instead of numbers, you have arrays of something else, but numbers are used whenever possible, such as to represent boolean values.) This representation is very powerful, but just as important is the uniformity in how it’s treated.
Variation in number is one of the biggest sources of irregularity in mainstream von Neumann languages. An array is a fundamentally different thing from a normal object representing a single thing. An object reference may refer to one value of its declared type, or it may be “zero” if the reference is null. A one-dimensional array is a different thing from an array of arrays, which may be different still from a two-dimensional array, if such a construct is natively supported.
The problem is not the variations in number and dimension, so much as the operations those languages offer for interacting with them. In Java, null is not an object. In Ruby it is, which is an improvement, but it probably doesn’t implement the methods you want to call on it. In all of these languages, iteration over arrays of arrays requires nested loops in proportion to the array depth, if you want to deal with the atomic values.
In J, all operations work on arrays (or at least all of the operations I’ve learned so far). It doesn’t matter whether you have one or a thousand elements in your array,
+ still adds corresponding elements. A number is just a zero-dimensional array containing a number, so adding to numbers is actually the same as adding corresponding elements of a 1-dimensional array, only different in dimension.
Dimensionality is handled with similar regularity. Dimensions don’t even necessarily need to match — if you add an integer to a matrix, every entry of the matrix is incremented by that integer. J solves the irregularity in number problem by providing fundamental operations that work uniformly on both scalars and aggregates, removing all of the complexity related to the subject in other languages.
The APL family is famously concise. Perl has a reputation for encouraging unreadable one-liners, but this family of languages takes that to an extreme. The funny thing is, where extreme concision turned me off to Perl, in J it’s mainly just intriguing (probably because of how I was introduced to it). Building large-scale applications in J may not be a good fit, but the concision works well in J for a couple of reasons.
First, J is mainly used in an interactive context. Programs are usually entered in REPL; not to build up massive edifice, but to explore and transform data. J’s concision reduces the distance between the programmer and their data. Short names for operations with minimal supporting syntax make programming more immediate and natural, requiring less time typing and more time thinking. There are times when I’m frustrated with Ruby or Java because their verbosity makes it frustratingly slow to get an idea written out — the syntax makes it feel like programming with numb fingers. Concision helps that.
Second, J’s operations are mathematical in nature. Operations in J are often very different from what you’re used to, and mapping them into English names just doesn’t make sense. They’re patterns of computation, rooted in mathematical properties of numbers and arrays. The hard part isn’t learning the name or the symbol, but understanding what the operation entails. The incomprehensibility of J isn’t really because of all the symbols — it really stems from the radically different nature of the operations it provides. The symbols have the dual benefit of being quick to type, and they avoid tricking people into thinking they understand what they really don’t. The fact that a monad’s core operations are called
bind (which, OK, is represented by a symbol) don’t reduce the number of blog posts about them. When you’re dealing with abstract, mathematical concepts, symbols work just fine because it’s the idea that’s actually hard.
Anyway, I’ve been having a lot of fun learning J. It’s like getting to visit an alien planet where the hard problems have been solved for centuries and you feel embarrassed for your species for not figuring out the answers already. The funny thing is, I still haven’t learned all of the core concepts in the language, but what I’ve learned so far is so bizarre and interesting and exciting, I’m happy to continue.