What makes the languages (APL, J, K) impressive is that you can simplify what is sometimes pages of code into a single line of symbols (APL) or standard ASCII characters (J and K).
Keep in mind that these languages are the definition of line noise to most coders as having prior knowledge of things like C, Python...etc will do you zero good as the building blocks are completely different from a high level. They might have if-statements in the language, but they're hardly idiomatic. J emphasizes tacit programming which is kind of like a data flow model made up of function application. The languages are quite mathy in some ways, but still approachable.
I find them to be lots of fun, very interactive, thought provoking, and pretty cool. I've only written a little APL & J, but enjoyed how I built my command up from a few built-in primitives.
A problem to me involves reading other people's code which uses primitives I haven't learned yet, or common idioms (Ex: you can make your own average function with a few characters avg=.+/%# so no need to add it to the stdlib). The average function on the tryapl.org site looked a little funny to me...then I realized it worked for not only lists, but multi-dimemsional data structures of n dimensions like tables or lists of tables. I imagine it takes awhile to become proficient.
Surprisingly, both J and APL have some decent support for things such as graphics, but they are still small communities compared to languages we know and love like Python.
In short, give it a try. J has some built in documentation called "J labs" that is submitted by the community. Dyalog APL has a free version for non commercial use and the excellent tryapl.org. Try not to be turned off by the bizzare symbols in APL. They have a long history and are easy to enter with either the IDE or keyboard (especially if you buy one from Dyalog with the symbols on the keys). The inventor of APL got the Turing Award awhile back (later invented J). It was also a big IBM product back in the day and is still used by companies like Volvo.
It would be great if someone could translate that into J, then I might have a chance of understanding it properly.
Code summary:
r =. (3 3 $ i. 9) e. 1 2 3 4 7
R =. _1 |. _2 |."1 (5 7 {. r)
RR =. 15 35 {. _10 _20 {. R
life =. verb : '+./ > (1;y) *.each 3 4 =each +each/ +each/ 1 0 _1 |.each/ 1 0 _1 |."1 each <y'
gen =. dyad : 'life ^: y x'
The 20th generation: (RR gen 20) { '.X'
...................................
...................................
...................................
...................................
.........X....XXX..................
........XX.....XX..................
.......XX.....XX...................
........X.X...X.X..................
.........XX....XXXX................
..........X...X..XX................
..............X.XX.................
..............XXX..................
...................................
...................................
...................................
Transcript of the whole talk with line by line translation here: https://dpaste.de/OoEM/raw (expires in 7 days).APL, rather than J, but here's a beautiful Prime Numbers up to 20 example:
(~R∊(R∘.×R))/R←1↓⍳20
Reading right to left, iota 20 makes numbers 1 through 20 inclusive, 1 drop takes that list as an argument and removes the first element, R left arrow takes that shorter list as an argument and stores the numbers 2 through 20 into variable R.In the nested parens, the outer product multiplication pattern (R___R) makes a multiplication table of the vector R against itself, and because we dropped 1 from the list we avoid all the 1xN and Nx1 pairs so it's a table of composite numbers. Then outer parens R epsilon compares each item in R with the matrix of composite numbers and generates a boolean result vector the same size and shape as R, but each input number has become 1 if it was found as a composite number, 0 if it wasn't (aka prime). Negate that bitmask so 1 indicates where a prime number was in the original R numbers, and use / to filter (reduce) the original list to pick those out.
Primes: Numbers 2..N which aren't members of all the products of 2..N.
No explicit loops, no if/else branching, no proliferation of names for working arrays, no need to wrap it in a function, give the function a name, document the function parameters and their assumptions. And even though it is N-squared runtime, it's short enough that an interpreter could pick out that pattern in code and implement it faster behind the scenes, if that was beneficial, without forcing the programmer to interrupt neat code to appease the machine beneath.
I can't APL or J, but I transliterate it to J as:
(-. R e. ; R*/ R=: 2 }. i. 20) # R
J starts counting at 0 so this drops two numbers from the list. (Yes, J has p: for making Prime numbers).I think this attributes to why people (not you I believe) find j/k write-only unless they are experienced. Most seem not to be able to imagine that because it is so terse, to compare reading an a4 sheet of k/j compares to reading 50 java files; if you think of it like that, and imagine those java files using some design patterns you did not know before, you can have at least some form of comparison. In that case I find k/j far easier and faster to read. I need to figure out what those few primitives do and how they work but that will take me less time than unravelling all the abstrzctions across all files to an extend I can start customizing code, while in k/j I am ready to jump in. ymmv ofcourse but I think too many people see the code and compare it to a lovely clean OO class in one file, or some a4 of Python and think wtf this is line noise!
That should be plenty of examples for you.