r/apljk on reddit is also active.
never seems to make these lists :)
if you don't mind me asking, do you also remember the day when you wrote some non-trivial program in any language and it "worked" the first time, whatever that means (i presume "correctly")? what was the language? are you sure you've made a full recovery from that shock as well?
on a serious note, APL (and, by proxy, its descendants) invented REPL (dubbed "dialogue approach") long before the people who coined "REPL" even came to be. When that happened, C lanugage wasn't around either. Fortran was, granted, and sure enough it "worked" every time. you didn't even have to try, just punch it up on a punchcard, stick it in, wait a while. done. flawless.
on a closing note: writing correct programs takes skill and happens in iterations. the faster you can iterate, the faster you can justify your money's worth. the less you type, the more you think. less code less bug.
does that help?
I have been programming professionally for about 30 years at this point, so it has happened to me literally hundreds of times at this point in at least 10 other languages- in fact in some languages (especially Haskell and Rust) I would say it's the norm for me rather than the exception to have the code work correctly if it passes compilation (which is sort of the point of strict type systems obviously).
It literally only ever happened the one time in kdb which is why I remember it so vividly and not in those other languages.
I have no idea why you thought your closing note might help, but sure.
[1] Depending on language obviously.
While I have enjoyed learning array languages, and think they are still underrated, Wikipedia seems to disagree with this statement above.
According to wikiepdia, REPL seems to have been coined after Iverson created his notation, but before the first APL was ever written.
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93prin...
no, actually i think Wikipedia got it right:
"The 1974 Maclisp reference manual by David A. Moon attests "Read-eval-print loop" on page 89, but does not use the acronym REPL.[7]
Since at least the 1980s, the abbreviations REP Loop and REPL are attested in the context of Scheme.[8][9]"
The first APL interpreter was implemented on System/360 in 1965. Iverson got a Turing in 1979.If you're arguing that "Read-Eval-Print cycle" doesn't count as REPL, then it pretty strongly undercuts your argument that "dialog approach" is.
And 1964 predates APL implementation.
no, thankfully, it doesn't, and here's why. as i elucidated above, PDP-1 was indeed a revolutionary "el cheapo" computer, which DEC managed to ship over 50 units of. it gave birth to hacker culture, just because it had to be hacked with all kinds of peg legs in order to be useful. now, to the point:
1. PDP-1 is mostly remembered for Spacewar!, a groundbreaking space combat game invented by a dude who also coincidentally invented large parts of LISP while on IBM payroll.
2. APL, after its debut in 1965-1966, had its first official application to teach formal methods in systems design at NASA Goddard Center.
My point should be obvious, but just in case:
in our line of work (between sessions on Hacker News) we are sometimes faced with the concept of "production" (usually on Friday afternoons). this idea really matters. it makes all the difference between fooling around and the real deal.
therefore, as it must follow, and as i mentioned much earlier, APL was and remains the very first real REPL system, although they didn't really use that terminology. all i meant is that in 21st century people take Chrome's devconsole, ipython, node, zsh for granted. with completions, hints, all that.
(in APL'esque family, by the way, there's hardly anything to hint or auto-complete. mathematics doesn't work that way. by the way, not coincidentally, one of the most successful modern descendants of APL is called Wolfram Mathematica)
are we on the same page?
APL\360 used a much more advanced selectric with a dedicated typeball, was designed for a machine of a totally different class, and could not be compared to literally kilometers of paper containing mostly mistyped parentheses. it was TRULY terse, expressive and interactive.
but yeah, PDP-1 hackers technically got there first - they had no choice :) check this out:
https://s3data.computerhistory.org/pdp-1/DEC.pdp_1.1964.1026...
6-7 Input and Output, System Operation is where the READ-EVAL-PRINT is indeed mentioned. but if you read the entire paragraph, and the next one ("if the system drops dead" lol) you'll agree that PDP-1 "REPL" was hell on earth, at the very end of the manual there are some really juicy REPL expressions :)
LISP was a torture compared to short and powerful APL notation.
less code less bug.
+/%#
to people. But the real expressive power comes when you start to get into tacit expressions yourself, understand function exponents, and "get" under.
Hmmm... maybe I need a refresher...
And lots of the "labs" (interactive tutorials) are built in too: 3 dots -> Help -> Labs..
Edit: I should also have mentioned that Termux on android can build ngn-k, goal and a bunch of other open source array languages too.
There's also Rob Pike's Ivy language for Android too.
Based on the one thing I remember in APL I'm guessing the first two characters are "sum over some data structure" and the data structure is what the next two mean. What does it mean entirely?
+/ sums the items of the array.
# counts the number of items in the array.
% divides the sum by the number of items.
empty =. 0$0 NB. zero length array
+/empty
0
#empty
0
0%0
0 a.sum() / a.count()
you would not need an explanation.we call them "trains". since this one has a descriptive name, `avg a`, is not cryptic at all. just a bit fewer of absolutely meaningless parens and duplication.
but it doesn't end there:
1. imagine you wanted a moving average instead. i need to change one character in this train to get `mavg`. what would you need to do?
2. imagine you want to compute moving averages for each of 1000 arrays using both instruction-level parallelism and all available cores? while you'd be writing your unobfuscated code for that, i'd be done way before you're back from your lunch break. in two keystrokes.
once you'd be done with your solution, there would be no need to discuss productivity, or so i hope, but to discuss performance would be interesting.
Vs=: 4r3p1"_ * ] ^ 3:
just reads normally. It's been over ten years since I did anything in J and I can almost explain that now...
What's the "_ for?
avg =: +/%#
then we can say
avg 2 4
and get 3
We can also say:
x =: i.2 3 4
and that makes an array like so:
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
16 17 18 19
20 21 22 23
And then we can do things like: avg"1 x
1.5 5.5 9.5
13.5 17.5 21.5
avg"2 y
4 5 6 7
16 17 18 19
avg"3 y
6 7 8 9
10 11 12 13
14 15 16 17
In this instance I believe the "_ is just giving back 4r3p1 = 4.18879 for each of the arguments handed to the rest of the verb. This is one of those things that is second nature once you internalize J, but so so foreign until you do.I recognised the " as rank but got confused by the _ reading https://code.jsoftware.com/wiki/Vocabulary/under, it seemed to be 'rank negative' and didn't see what that did.
> second nature once you internalize J, but so so foreign until you do.
Indeed!
definitely. tacit can be taken a bit too far sometimes, but when a certain discipline is observed (just like in any language, really) it is no less readable than... let me see. oh, lets take some typical pandas/polars heroics - no, those things don't give me brain aneurisms and not too shocking either. they just make wanna vomit no less than the poor people who were forced to write it and contemplate what they've done.
> tacit is "bad"
not at all. it takes one time to see how avg looks in explicit notation to understand that the idea of trains is totally justified.
> the question of how to thread data around without naming it is an interesting one to me.
good question. see above - yes indeed, things can be taken to extremes. when an apl/k program is a oneliner 80 chars long, that's just not cool. there is no reason not to break it down a bit into moving parts with names (and ample space on the right margin for annotations). in no way APL and friends are somehow exempt from commenting their code. but that's not really endemic to array languages, you'd agree.
127 * sin (range sample_rate)*2*pi*freq_hz/sample_rate
This produces one second audio-clip of a "freq_hz" sine-wave, at the given sample-rate. The "range sample_rate" produces a list of integers from 0 to sample_rate, and all the other multiplications and divisions vectorise to apply to every item in the list. Even the "sin" operator transparently works on a list.It also took me a little while to get used to the operator precedence (always right-to-left, no matter what), but it does indeed make expressions (and the compiler) simpler. The other thing that impresses me is being able to say:
maximum:if x > y x else y end
...without grouping symbols around the condition or the statements. Well, I guess "end" is kind of a grouping symbol, but the language feels very clean and concise and fluent. # python
[127 * sin(x * tau * freq / samplerate) for x in range(samplerate)] # python
from numpy import sin, arange, pi
127 * sin(arange(samplerate) * 2 * pi * freq / samplerate)but enough talking about languages that suck. let's talk about python!
i'm not some braniac on a nerd patrol, i'm a simple guy and i write simple programs, so i need simple things. let's say i want an identity matrix of order x*x.
nothing simpler. i just chose one of 6 versions of python found on my system, create a venv, activate it, pip install numpy (and a terabyte of its dependencies), and that's it - i got my matrix straight away. i absolutely love it:
np.tile(np.concatenate([[1],x*[0]]),x)[:x*x].reshape(*2*[x])
and now lets see just how obscure and unreadable exactly the same thing looks in k: (2#x)#1,x#0
no wonder innocent people end up with brain aneurisms and nervous breakdowns. np.identity(n)
http://numpy.org/doc/stable/reference/generated/numpy.identi...assuming you're referring to numpy as to have anything to do with python spec, i totally agree with you. only it doesn't. so don't pytorch and pandas (and good so, poor python doesn't need any extra help to be completely f).
> you get an nxn identity matrix by...
no, man, that's how you get it. really advanced technique, kudos!
i get it by:
id:{...} /there are many ways to implement identity in k, and it's fun!
id 3
+1.00 +0.00 +0.00
+0.00 +1.00 +0.00
+0.00 +0.00 +1.00
but if you can keep a secret, more recently we've gotten so lazy and disingenuous in k land, and because we need them bloody matrices so often now, we just do it like so: &3
+1.00 +1.00 +1.00
+1.00 +1.00 +1.00
+1.00 +1.00 +1.00
=3
+1.00 +0.00 +0.00
+0.00 +1.00 +0.00
+0.00 +0.00 +1.00
(but of course before we do that we first install python4, numpy, pytorch, pandas and polars - not because we need them, just to feel like seasoned professionals who know what they're doing) t=\:t:!xthis is of course obvious first idea, but the recipe from above is actually from the official k4 cookbook. t=t is less innocent than it seems, i'm afraid.
in k7/k9, we can:
10^@[100#0.;11*!10;1.] /just for more lulz
there's also a way to mutate it in place!https://news.ycombinator.com/item?id=45603661
HN is such a sweetheart. i should check in more often.
foo(x)
...while the function that calculates a batch of values looks like: [foo(x) for x in somelist]
Meanwhile in Lil (and I'd guess APL and K), the one function works in both situations.You can get some nice speed-ups in Python by pushing iteration into a list comprehension, because it's more specialised in the byte-code than a for loop. It's a lot easier in Lil, since it often Just Works.
only typical k binary will be less than 200kb and doesn't need stdlib. it still needs a few syscalls, but we're working on that.
and julia has this small and insignificant dependency called llvm. i bullshit you not:
kelas@prng ~ % cd /opt/llvm-project
kelas@prng llvm-project % du -hd0
14G .
kelas@prng llvm-project %Most (not all) APL code I've seen uses very short names, often one letter names, for function names. And APL programmers are famous for cataloging "idiom" which are short phrases for common subroutines. In other words, it's best practice to repeat 3- or 4- symbol phrases instead of defining a subroutine.
Of course, there's nothing about an array language that requires using symbols; but for some reason most do.
The idioms become words and you read them like words, you don't step through each letter of a word when you read it, you recognize the shape. The same thing happens in APL and its ilk, any commonly used sequence is instantly understood as its function without having to parse each individual symbol and what it does.
higher-order functions (over, scan, each, etc) are called adverbs.
sure, that's a very useful feature, like elsewhere.
> I wonder about a purely aesthetic issue: how does it look to interleave those symbols with user-defined words that by nature will be much, much longer, i.e. "create-log-entry" or "calculate-estimated-revenue".
strictly speaking, dashes and underscores in k can't even be a part of identifier - they are core language primitives. it is very uncommon to see java-like identifiers like CalculateEstimatedRevenue, why would you want that?
to your question:
here's a bit of an oddity: all user-defined functions and core language operators can be called using functional notation:
v:1 2 3 / some vector
v+v / usual infix notation, two operands: left and right
2 4 6
+[v;v] / same as infix, but called as it were a function.
2 4 6
add:{x+y} / a user-defined function: a lambda with a name and two operands.
add[v;v]
2 4 6
but there is an important distinction between the two. you can't use your `add` function infix, you must call it as a function, and there are good reasons for that: 2 add 2 / that's not gonna work
that said, mixing language primitives with function calls looks and reads just fine: +/add[v;v]
12
hope this helps!So does it end up as
v_sepallength: 11 14 12
v_sepallthickness: 1.3 1.5 1.2
mul[v_sepallength;v_sepalthickness]
Or, do you just not do that sort of stuff in these languages? I'm not very familiar with them, but I have ended up with some pretty long programs using Pandas in Python.i tell you more. it is very much recommended to avoid doing this sort of stuff in all languages.
v_sepallength: 11 14 12
v_sepallthickness: 1.3 1.5 1.2
mul[v_sepallength;v_sepalthickness]
no: /sepal:lengths and stroke widths
spl:[l:11 14 12;w:1.3 1.5 1.2] /this is your "struct", if you will
spl.w
1.3 1.5 1.2 /proof
*/spl
14.3 21. 14.4 /for mul, we don't even have to bother with field names
*/spl`l`w /but if you insist, lets make it explicit
14.3 21. 14.4
to produce a "factory" for well-formed spl objects is a no-brainer as well.why we don't use v_ prefix:
1. everything what can be a vector should be a vector.
2. we can't use underscore anyway - it is an operator.less important things can have longer names. variables in a broader scope can have longer names.
if you have a hundred different vectors, don't just dump them in a pile; put them in dictionaries, tables, namespaces, or scopes.
the complexity is built differently in k.
* namespaces do exist, and are just as useful as they are in c++ and especially my beloved *sun.misc.unsafe*. i recommend.
* instead of passing 20 arguments to a function (which is impossible - the limit is lower), we pass a dictionary if we have to. k **pretends** that everything is passed by value, but in reality it is much smarter than that.
* notion of *scopes* is a bit of a non-sequitur here, but it is fundamentally important that there is no *lexical scoping* in k. the only two scopes which are available from the scope of a lambda are exactly *local* and *global*. and for as long as your function doesn't mess around with global scope or i/o (which is essentially the same thing), it remains pure, which is super cool. this design is not just for simplicity - it is for a good reason, and more than one.
* the above doesn't mean that it is impossible to create a *closure* in k and pass it around as a value.
* functions take up to three implicit arguments - named x,y and z (they can be renamed explicitly, but why not just document their semantics instead, in-situ?). all you need to do to declare xyz is reference them in the function definition. in competent k code, you'll rarely see a function with more than xyz.
* in k community, we don't use upper case unless the apartment is on fire. god forbid.
* shorter names and more documentation, and there will be joy.Also missing Uiua.
The REPL is what matters - also while being performant.
Someone asks you a question, you write something, you run it and say an answer, the next question is asked etc.
I've seen these tools be invaluable in that model, over "write software, compile and run a thousand times" problems which C/Fortran lives in.
But for the loss of control you get stuff like fancy SIMD implementation for nothing.
All and all there’s a cost/benefit calculation but that ratio can get quite low.
it is not a Kunstkamera or some computer cryptozoology extravaganza. many things are shown here, from different eras, but some are not - they are evolving. and progress takes sacrifice.
cheers k.
ps. we usually spell atw as atw :) he also goes by a. don't chicken out, send him an email. he's a very friendly guy. just like me.