The Problem With APLs
hillelwayne.com
hillelwayne.com
As in most language learning, one starts with a small number (20?) of words and expands from there. APL also requires the neophyte to develop a mind mapping from abstract symbol to function. This may double initial learning time but I don't think that it's much worse than that. Mnemonics exist for many of the symbols.
But more fundamental is learning to map the problem space into an array formulation. This, not symbol application, is where a white board may come in handy.
Dyalog provides a free web-based tutorial that is quite comprehensive. [1] I daresay that spending a couple of hours with this tool will give you a great taste for APL. Spend a couple of days and you'll be quite proficient in both array-think and APL semantics.
Other useful tools include the wiki [2], a cloud server [3], and a neat examples wiki [4]. BTW, the "library of useful components" that the OP desires exists both as a compilation of useful idioms (code fragments) [5].
[1] https://tutorial.dyalog.com/ [2] https://aplwiki.com/ [3] http://www.aplcloud.com/ [4] http://www.jsoftware.com/papers/50/ [5] https://aplwiki.com/FinnAplIdiomLibrary
The approach I've taken, which I think is common and slow and easy to "fall off the bandwagon", is to write my solutions to my problems on my blog or email the J mailing list and see if the experts can improve on my code. They always can, if they care enough to show me, and they usually do. But as I mentioned in my other comment, I think one of the wonders of APL/J/etc. is that it seems to share even less with conventional programming languages than it seems to at first because the way APL programmers decompose problems is radically different. I often find myself trolling through the dictionary looking for things that I wouldn't need if I didn't break the program down into as small pieces. Choosing the right verb is partly tactical, but the whole strategy for attacking problems is so unconventional you are often looking from the wrong vantage point.
I don't know if there is a royal road to APL that addresses this, but I share Hillel's desire for one. I suspect there isn't, and the only way to really get better is: read lots of code, write lots of code, get feedback on your code, repeat.
Honestly this is a problem with programming in general. A lot of modern frameworks actively work to maintain the problem by innovating with layers that break even the modest discoverability achievements that have been made, such as IDE autocomplete and "jump to declaration".
This problem is endemic and not due to any particular language or company. To pick a few examples, Redux, Rails and Angular each offer their own flavors of this issue. In the name of making things visually simpler or conceptually more structured, they make it much harder to understand who's calling your code and what calls are available to you — the very basics of programming discoverability.
This confrontation with an alien culture of computing seems to make us recoil in horror for the most part. But having been over that barrier with SQL and Prolog, I find it plausible that the APLs really do represent a more elegant approach to computation that just requires a much larger up-front investment.
Like Hillel, I have often thought that my solution to a problem in J was good, only to see much shorter solutions from prominent J programmers. What makes it shorter is usually that there are direct but less obvious ways of doing things using the primitives. I have the experience of having a first look at things like grade-up and remembering it as a way of sorting, but when you see the various ways that grade-up is used in practice, there are a lot of uses for it that do not strictly have to do with sorting. This is in contrast to Python where lists can be sorted and that's all sorting does for you. I frequently forgot about the match builtin and would do convoluted things like equals with rank (0 1) and insert-equals or sum. There's more than one way to do things, but often beginner code is obviously worse.
A programmer has an idea of what they want the program to do, expressed in human language and concepts. Programming involves refining this idea in sufficient detail that it can be expressed as a program, replacing the imprecise human thought with precise symbols that will be mechanically interpreted. A programming language offers a set of primitive symbols from which larger concepts can be built.
One reason people avoid programming in assembler much is that the primitives are "too small": they don't relate well to the concepts the programmer is thinking in and you need a lot of them. I wonder if the problem with APL-style languages is that, unless you're used to operating in a mathematical domain, the primitives are "too compressed": a single symbol standing for a large complex concept.
https://confengine.com/functional-conf-2017/proposal/4620/de...
Writing code in a scalar language makes you rather like a general who gives orders to his troops by visiting each one and whispering in his ear. That touch-of-Harry kind of generalling can achieve victorious results, and it has the advantage that the orders can be tailored to the man, but its disadvantages are significant: the general spends much mental energy in formulating individual orders and much breath in communicating them individually; more significant, his limited attention is drawn toward individuals and away from the army as a whole. Even the great Rommel was overtaxed at times.
The J programmer is, in contrast, a general who stands before his army and snaps out orders to the army as a whole. Every man receives the same order, but the order itself contains enough detail for the individual men to act appropriately. Such a general can command a corps as easily as a platoon, and always has the 'big picture' in mind.
Still, this isn't as ironic as when folks coined "DRY" (Don't Repeat Yourself) when they meant "refactoring", thus repeating the idea itself.
Refactoring is about 90% of programming (the other 90% is parsing, or is it naming?) and if you're into it you should check out Forth (if you haven't already) specifically "Thinking Forth" http://thinking-forth.sourceforge.net/
It would probably take a beginner python programmer 40+ minutes to write the mode function too.
J might have a lot of primitives, but for an example that might be more familiar, so does assembly and people got along just fine there. There is an investment in getting the hang of the primitives but there aren't a huge amount of them, and once you do a problem like this becomes much easier.
The other thing that's really nice is that you start to see how to solve it in different ways using completely different sets of primitives.
Normal languages only have about 20 unnamed functions (i.e. operators) and they're mostly things we've already learnt at school.
>Normal languages
Who's to say what "normal" is? There is a learning curve to other languages too, people just forget that because they went through it so long ago.
Is % an unnamed function? Or {. ? Then you have lots of unnamed things in, say, Java, which are "public", "static", "char" etc...
You don't need to learn them all before you can start doing something useful. J for C PRogrammers provides a good course gently introducing simple ones first.
There's some nice magic in how quickly you can write mode as an expert, and part of the appeals of APLs are the power of some of the built-ins. There's likely other tools that would be more worthwhile in the `:;` spot than its current definition.
It's a pretty common idea "let's rename all those ugly APL primitives with descriptive names". And it's easy to do. Why do you think the idea doesn't stick in APL, even though it works in say Matlab? Maybe it is because for APL programmers \. is perceived on the same level as + and you don't ask to rename + to 'plus'.
This is the TXR Lisp interactive listener of TXR 197.
Quit with :quit or Ctrl-D on empty line. Ctrl-X ? for cheatsheet.
1> [group-by identity '(1 1 1 1 2 2 3 3 3 3 3 3 4)]
#H(() (1 (1 1 1 1)) (2 (2 2)) (3 (3 3 3 3 3 3)) (4 (4)))
2> [find-max *1]
(4 4)
3> [find-max *1 : len]
(3 3 3 3 3 3 3)
4> (car *3)
3
All together: 5> (car [find-max [group-by identity '(1 1 1 1 2 2 3 3 3 3 3 3 4)] : len])
3
Build an anon function out of these steps using opip macro: 6> (opip (group-by identity) (find-max @1 : len) car)
#<intrinsic fun: 0 param + variadic>
Invoke it on the sequence: 7> [*6 '(1 1 1 1 2 2 3 3 3 3 3 3 3 4)]
3There's a good analogy here with human languages --- APL is like Chinese, whereas Python is closer to English.
The whole point of APL is that it doesn't try to appear like an existing human language, so the concepts relevant for programming can be expressed with more concision.
You do APL like you do mathematics: sit down with pen & paper and think. And yes, many times it’s hit-n-miss, and someone walking by will tell you how it’s trivial with technique X you were unaware of. But again, this is how people do maths too: Maxwell’s original set of equations for electromagnetism were 20 differential equations, and it was later that Heaviside came by and rewrote it in its concise vector form we all know. (And those four equations can still be simplified to a single equation via geometric algebra! https://en.m.wikipedia.org/wiki/Mathematical_descriptions_of... )
Substitute X/REPL.
The problem I have is that when I gradually get to a solution in Lisp/Ruby/Python, I end up with something which is also close to lots of other problems I want to solve (or that my manager or users ask for). In solving the problem, I've naturally built up tools for that entire problem space.
In APL, I end up building a solution which is not at all close to other problems I might want to solve. I'll take an identity, flip this, zip it with that other array, reduce it with this function, and then poof, the final step slides all the puzzle pieces together and it's perfect (and only 17 characters!), but only for that problem exactly as stated. When I get a request for something slightly different, I almost have to start from scratch.
I assume this is what the "beautiful diamond" / "ball of mud" dichotomy referred to.
You don't really start from scratch though (at least I don't): most of the time you can reuse idioms with minimal adaptation.
Do you start over from scratch in terms of your mental model or just in terms of the actual code?
I’ve found using APL helps me to generalize a problem but the underlying code will often look very different.
I have found I waste a lot of time in other languages trying to re-use components that aren’t necessarily a good fit just because I have already written them.
My experience is the exact opposite.
A few years ago when I taught someone enough K (an APLish language) to explore a data-related problem and documented it here[1].
[1]: https://news.ycombinator.com/item?id=9122299
And for what it's worth, I don't do mathematics. I write software.
This is similar to how languages of the Lisp family are called 'Lisps'.
Also, languages like K and J, maybe Q could be considered part of APL family due to their array oriented design and single character operator syntax.