@ASCII-only First, and most importantly, forget that APL is a programming language. It is just traditional mathematical notation (TMN) which has been harmonised and generalised.
This makes it a good choice for communicating algorithms between humans, and coincidentally, it makes it possible to create an automated evaluator, i.e. use it as a programming language.
@ASCII-only In this chat room lives a bot which looks for messages beginning with ⎕← and evaluates the rest of the line as APL. If you begin your lines with four spaces, it will look better and avoid strange markdown effects.
@ASCII-only OK, let's look at the first bit of harmonisation/generalisation APL does to TMN. TMN suffers from wildly inconsistent syntaxes, e.g 2³, log₅ 4, sin ⍺, –4, 4!, |z|, f(x), etc. etc.
APL takes the most common one, -4 and generalises TMN so all single-argument ("monadic") functions are prefix without needing any parens. Two-argument ("dyadic") function application consistently gets an infix symbol. So the above are 2*3, 5⍟4, 1○⍺, !4, |z, f x in APL. There are no exceptions to this.
@ASCII-only As it follows naturally from TMN things like f(g(h(x)), which in APL of course is f g h x, all functions have long right scope (and thus short left scope). APL generalises this to even apply to arithmetic functions, so you don't have to worry about precedence. All functions have long right scope. No exceptions. E.g.:
@ASCII-only OK, I'll be more brief. Better start verbose than leaving people in the dust. And I can't exactly look at your facial expression to gather whether you're lost or bored :-)
@ASCII-only OK, next is APL's generalisation of higher-order functions. They are called operators in APL. E.g. TMN's ∑ and ∏ are just + and × reductions over a list, written in APL as f/ so +/ and ×/ but works with all dyadic functions.
Operators have long left scope. May seem inconsistent, but you'll quickly find it natural.
Operators are used for a wide variety of higher order concepts, from simple things like mapping with "each" f¨ over generalised linear algebra (e.g. dot/cross/matrix product is a specific case of the generalised f.g where f←+ and g←× giving +.×) to launching f(x) in a separate green thread with f& x
@ASCII-only The operators are stronger than functions. The left "operand" of an operator must be either an array or a function. The operator will "grab" to the left until the beginning of the immediate array or function. E.g. in 1 x op y the operator op will have x as operand if x is a function but if x is an array, op will only the entire array (1 x) as left operand. Sounds complicated, but it is really quite simple in action.
@ASCII-only Yes. And there are no "strings", only 1-dimentional character arrays.
@chrispsn Whoa, so AW is going to make his own "C" compiler which both allows him a faster K build-cycle, and makes the K executable even smaller. I expect K to eventually be faster than not just hand crafted C, but also equivalent C.
@ASCII-only If that has sunk in, we can proceed with a very basic function, the index generator ⍳:
@ASCII-only No, I mean that the exact C code that "runs" behind the covers when K code is executed would be slower than the exact B code that runs behind Kx.
@Adám What surprises me is: 'b' is not new. It was uploaded to kparc in 2016. It shows some "raw, contemporary Whitney" in action - but few (@ThomasLackner notably excluded) have leapt upon it and analysed it in detail, in public, despite cries to make k itself open-source.
@chrispsn i rediscovered my notes on that recently on an old server. i'd love to discuss more. "b" is certainly a panacea of currently-available alien whitney tech.
@ASCII-only Normally, it is theoretically possible to write C code that matches the speed of K (or APL), but it may require a lot more C code than what a C programmer normally would write — so much more that even a careful coder is likely to fail to match the speed. But if K is implemented in C, it may even be impossible to match its speed in C, and one would have to write assembler to match, which is of course impractical.
@ASCII-only k has some common operators like "in" and "except" short circuited to work faster on sorted vectors. that kinda thing can be done in C, but is a challenge to integrate due to C's clumsy type overloading.
@chrispsn the first time i came across the AW style of parsing i had to shower intensely, so deeply shook was i. B has a lot of those same threads. Did you ever come across Effibae's C vector library? it helped me understand B, for some reason.
@ASCII-only in fact, if you can figure out w0 and w1, you'll probably have to make significant life changes to cope with teh blistering reality that THAT is lex and yacc
@ASCII-only As an example of a dyadic operator which derives a dyadic function, we have ⍣ where X(f⍣k)Y applies f to Y, k times, each time with X as left argument:
@ASCII-only i'll explain. most of the time when you lex or parse something you work with some abstract notation that involves symbols, states, all that. in those two definitions (w0 and w1 inside s.k, which is an SQL parser for K) he uses purely vector-oriented math operations to extract a surprising amount of structured info
@ASCII-only he uses a bunch of arrays to work as character -> state transition tables. it makes sense if you pick it apart. like i said, about 80 hours minimum
@ASCII-only If we remove the parens 2*⍣2 1 2 3 4 then ⍣ won't grab the leftmost 2 because it only reaches left until the left of *, but on the right, it will catch all of 2 1 2 3 4 so the statement won't have an argument, and will therefore fail:
@ASCII-only one of the great things about AW's style (and many people in the vector community) is that it starts with the simple, low level definitions at the top, and contextually adds layers of meaning, until the test cases at the bottom. after you get used to it, it makes it a lot easier to follow - its like a text
@ASCII-only if you're a work-a-day coder both k and apl are pretty productive, though the learning curve is steep. if you are ok with getting deep with things, its a much more pure way to work.
@ASCII-only like your boss tells you to do dreadful things and you have to do them or you become homeless. typical daily coder. as opposed to maybe a researcher, or someone doing education, or a manager..
@ASCII-only i wish i had studied vector languages at that point in my career. Adam is here to help! :)
@ASCII-only there are so many to choose from. i think this is the fashionable one for k6, the k that we were supposed to get, which has been replaced by k7. AW is extremely terse but precise - all punctuation and spacing in this document has meaning. kparc.com/k.txt
@ThomasLackner Not a bad choice, but I dislike the differing syntaxes between dfns and trains.
Especially since trains and dfns both a special cases of "tacit functions". So if I can write
avg←sum÷num;
sum←+⌿;
num←≢
why can I not write
avg←{sum÷num};
sum←+⌿;
num←≢
And how exactly am I supposed to write multiple levels in the tacit way. Compare:
avg←{
sum÷num
; sum←plus⌿
; ; plus←+
;num←≢
}
with
avg←sum÷num;
sum←plus⌿;;
plus←+;
num←≢
I find the latter completely incomprehensible.
So then you might say, oh, lets take the clearly superior dfn "where" syntax and use it for tacit functions too, but that won't work, as tacit functions don't have explicit encapsulation like {…} so the interpreter cannot know to look ahead (unless we do something really funky, imho).
There are also some clarification questions. Which (or both) parts of a guard does the where clause qualify? Can I write this on a single line somehow?
Does this introduce a scope for tacit functions?
Maybe the ;;; can work with disregard to line breaks, and tacit functions could use parens to limit themselves when on a single line. (⍳;⎕IO←0) would be neat, and possibly better (faster, more versatile) than my proposed (⎕IO:0).⍳
@ThomasLackner Does ^, ^^, ^^^,… answer your question?
@Adám I'd think that semicolons would be an alternate line separator to ⋄ - just executing in reverse, so multiple semicolons would be pointless - avg←sum÷num; sum←(plus⌿; plus←+); num←≢
(or maybe (sum←plus⌿; plus←+)? of course, the parenthesis are mostly pointless anyway)
which brings up the question - how would variable scope work with that? I'd expect (a←b+1; b←5) to create a variable a but leave b undefined out of the expressions
also reminds me that at some point i wanted to try to add k's views to my apl
outer parens are pointless there, but it's a good question what'd be the best way to space that
@Adám also it still works with the reversing idea - foo←{moo←,⍨ ⋄ (moo←⌽ ⋄ goo←{moo⍵}) ⋄ moo(goo⍺),(goo⍵)} (though with ⋄ in expressions and variables not escaping their defining scope (which are also things i've wanted to add to my apl too))
while at this, i could also finally maybe implement maps with non-string keys - how should that be handled - have a separate type for that, have all maps be universal maps or have a quiet transition between the two when needed?
@EthanSlota what are 95% of your for loop uses? I'd guess they're just for (int i = 0; i < someAm; i++) which is horrible boilerplate that APL gets rid of
(also I really ought to figure out how to use APL in production code some time but maybe that's in the manuals I keep forgetting to continue reading :/)
Also, how do you get the permutations of a vector in APL?
Also on the subject of what I think is called b from earlier today. Is this a new compiler that could make it faster than K to C, or a new language for K to be interpreted into, making it potentially faster than C?
@Sherlock9 K is inspired by APL, but does away with rank. It also has a very minimalistic set of primitives, but uses a larger set of data types together with heavily overloading the functions based on argument types.
@Sherlock9 Interestingly K people tend to write English in a specific style. Absolutely no formatting other than extra spaces, no uppercase letters, never any formatting.
so it seems my first solutions are about 50% away from the more traditional ones, but beyond that it's hard to tell much https://tio.run/##1VTNSsNAEL7nKYaCoDSp2c2mSUHQFHyCHnqph9K44CE9@HfR3sSDNiKKL@DJWy8KUhChj7IvorubbFrs2h5skrrkZzIz33wzO5vpdfvn3ZMvviLEru8vAv5g8dhE7PaqLeU3KU9G3PzYsjaRyeKXyQhb2yx@D7bY8DPgd9vi2hb3r3baAyPCIhR3Vn4yovDlAn8mwWwzAXCKgRFKdvnBQ407timxAMC/xAsUSAHwYkC1owBCIwDOIoCImwAAFIAsZrD3ELt5FqDhR5oU/VFFYKkk0n3VVULxctBcNdRZBtJURMlyJk1VTeBWQNLg7@wiWywWvyLbCKRpVmUbB8dn/dOj6BCsHlSaECEIKlLAqRAqTZhpHCWQVKDKhyofqnyo8DHgz4uHSLLjNTyAV3P2JyMCl9xibwC7eyrlSrPCWVZIZWWRkrJKdypUO0VqfpqT5birzknXox…
the ¯2-/ doesn't seem to do much to the speed
+\¯1↓0, seems to consistently be a bit better though, and it's the one that was linked as finnapl
@dzaima Interesting. I would have thought that ¯1↓ should be before +\ as that makes one less addition, and similarly for 0,. But both are O(1) so it doesn't matter much.
@dzaima Yes, but that'd need a thunk (which we don't have yet). That's why I'd expect 0@1⊢¯1⌽ and ¯2-/ to be faster. Anyway, the ref-count of ⍵ is more than 0, so it can't do in-place.
The ref-count of ⍵ is above 0, so we need a copy for the +\ any way. 0, causes the any way necessary duplication with minimum overhead. ¯1↓ is "free" (only the header needs to be updated with the decremented shape). Now +\ can be in-place because the ref-count of ¯1↓0,⍵ is 0. Finally, ¯2-/ can be calculated using vector instructions and without actually duplicating the vector.