Hacker News new | ask | show | jobs
by an1sotropy 1851 days ago
I had thought of APL as something from computing pre-history, with its bizarro custom keyboard, but I learned that APL and other array languages are apparently alive and well. Will subscribe to the podcast.

Two quotes the hosts brought up stuck with me:

(at 15:05) "A language that doesn't change the way you think is not a language worth learning". From Alan Perlis [1], and his Epigrams in Programming (#19) [2]

(at 16:49) "it is a privilege to learn a language/ a journey into the immediate". From poet Marilyn Hacker [3]; totally captivating idea, even if not not about programming languages [4]

[1] https://en.wikipedia.org/wiki/Alan_Perlis [2] https://cpsc.yale.edu/epigrams-programming [3] https://poets.org/academy-american-poets/winner/prizes/james... [4] https://www.enotes.com/topics/marilyn-hacker/critical-essays

2 comments

k (and the closely related q) is the main language used in industry, particularly at investment banks and hedge funds. It can be a bit of a shock to realise there are people in London earning in excess of £1000/day (pretty good for London) working in a language where well-written code looks like this[1]:

  us:{$[#i:&{(y~*K)&"*"~\*x}':x;@[x;i;:[;,"_"]];x]}
It's like discovering a whole different world of software development. Also I don't use that example to disparage k, I have come to appreciate the array language way-of-working. It just looks very alien.

[1] Real example found in a random script on https://nsl.com/: http://nsl.com/k9/sql.k

What the actual f?

It's like someone threw up the noise that modems make during initial connection onto an electric typewriter from the 1960s, and then explained their intention using quotes from a Lovecraft novel.

I am going to tell you something fantastic, but first, I want to explain some things about this:

    us:{$[#i:&{(y~*K)&"*"~*x}':x;@[x;i;:[;,"_"]];x]}
The first is that there's a typo in what bidirectional wrote. The above is correct. The second, is what it is. Once I have explained that, I can tell you the fantastic thing.

k syntax is very simple. There's just a few forms you need to be aware of:

    f x
which applies x to f.

    a f b
which is apply f to the two arguments a and b, and:

    f[a;b;c]
which allows you to do three arguments. You can write the first one as f[x] and the second as f[a;b] if you like even more consistency. f can be an "operator" -- that is a symbol. The symbols ' / and \ are special and called adverbs. These adverbs have a special form if followed by a colon, so ': is different than ' and has nothing to do with : or '. I think Arthur just ran out of keys on the keyboard. Once you have those, parenthesis () and braces {} have some special syntax, just like double-quotes " do.

With the syntax explained, let us try to understand what we are looking at.

us: is how we start assignment. You can say "us gets" if you like (the colon can be pronounced). {} braces surround a lambda, this one takes a single argument "x" (the first argument). $[a;b;c] is cond like in lisp; if a then b else c. # means count. i: is another assignment.

& means where -- the argument to which is going to be a bitmap like 000100b or 01101b or something like that, and where returns the indices of the set bits; the former example being the list 3, the latter example being the three-element list 1 2 4.

Another lambda comes next: We can see it takes two arguments because there's an x and a y in there (y is the second argument). We can get a clue as to what it expects because the following adverb ': means each-prior. This tells us "x" is going to be a list of things, and this lambda is going to consume them pairwise. If given the list {(x;y)}':"iliketacos" we get the result:

     {(x;y)}':"iliketacos"
    i 
    li
    il
    ki
    ek
    te
    at
    ca
    oc
    so
y is the "previous" value, and "x" is the current value. The "where" before it tells us we want to know the indices where the condition inside is true. Let's try and understand that condition.

y~*K is in parenthesis. Parenthesis group, so we execute them first (just like in other languages). We're looking for a situation where the previous value is the first (that's what asterisk means here) of K. What is K?

    K:("select";"distinct";"partition";"from";"where";"group";"having";"order";"limit")
So we're looking for a value (x) whose previous (y) is the first of K which is "select". The "&" that follows here is "and" - Arthur likes to overload operators since there aren't many symbols on the keyboard and this is something you get used to.

So you can read {(y~*K)&"*"~*x}':x as simply trying to find the sequences "select star" -- given a list ("select"; "*"; "from"; "potato") you get 0100b and from ("select"; "*"; "from"; "("; "select"; "*"; "from"; "potato; ")") you get 01000100b. I think the attempt is to disambiguate the asterisks in the sql:

    select * from tacos where cat=4*42
but sql is a strange and irregular language, so this kind of thing is necessary. Back to our query:

    us:{$[#i:&{(y~*K)&"*"~*x}':x;@[x;i;:[;,"_"]];x]}
i is going to be the locations of the asterisks following select. If the count of that is nonzero; we're going to do the @-part, and if not, we're just going to return x.

    @[x;i;f]
is called amend. It returns x, but at indices i, we apply them to f, so it's x[i]:f[x[i]] which is pretty cool. f in this case is a projection, of "gets" (the function colon) with the second-argument bound to an underscore. That is:

    :[;,"_"]
is just a function. That's how @[x;i;:[;,"_"] replaces all of the asterisks that follow select with an a "_"

Almost. It's actually a list of length one, rather than the scalar "_". I haven't read everything in sql.k but this is probably important elsewhere.

Ok. Now that I have explained what this is and what it does, I am ready to tell you something fantastic. I read this:

    us:{$[#i:&{(y~*K)&"*"~*x}':x;@[x;i;:[;,"_"]];x]}
as:

"us gets a function, that finds the indices of asterisk following the first element of K, and then replaces the things at those indices with underscores"

Literally. From left, to right. Just that fast. And I only program in k part-time. That's not the fantastic thing. The fantastic thing is that by learning to read k, I am almost miraculously able to read other languages faster. This:

    copied=False
    for i in range(1,len(a)):
      if a[i] == "*" and a[i-1] == "select":
        if not copied:
          copied = True
          a = a[:]
        a[i] = "_";
gives me some grief for being so irregular and gross, and I have to look up range/xrange and len and memorise a much more complex set of rules for syntax, and I have to track the order of things carefully and so on, but I have places in my brain, made by k, for those things, and so I am able to absorb code in other languages faster.

If that does not amaze you, I do not think you have considered the ramifications of what I said. I can suggest maybe reading it again (or maybe actually reading what I wrote instead of skipping to the punchline), but if after two or three tries you are still lost, maybe you can ask a question and I can try to answer it.

To me, this is infinitely more readable:

    let mut input = ["select", "*", "from", "potato"];
    for i in 1..input.len() {
        if ["select","*"] == input[i-1..=i] {
            input[i] = "_";
        }
    }
If I could be bothered to dig up a Haskell compiler, I'm sure it's possible to do a one-liner list comprehension that is both terse and readable.

If I was doing this in Rust, it's easy to create an iterator extension that does something like "map_lookback" which explains the intention without requiring comments.

I'm going to be blunt: Unreadable array languages are popular with quants because they work in a highly competitive, cut-throat industry where "write only" languages provide job security. I've come across developers purposefully obfuscating code by using only one-character identifiers and zero comments. One of them literally blackmailed their employer, demanding their salary be doubled on the grounds that there was no chance their code could be maintained by anyone else. He should have gone to jail for that, but he had his managers by the balls, got what he wanted, and bought a house with cash soon after.

Why are we applauding this?

> To me, this is infinitely more readable:

Yes, to you. Just like Chinese would be infinitely less readable to you, if you don't know Chinese.

Do you think this is a deep observation?

The real challenge is knowing whether it is worth it to learn k so that it becomes readable.

- How many characters is it? This is a useful metric when you realise bug/defect rate is proportional to the physical size (in rows and columns of source code) of a program given equivalent processes (Moore 1992; McConnell 1993) but that process matters more than anything else. Not tooling, not "memory safety", and certainly not "readability" by people unfamiliar with the language.

However "readable" you think your rust code is, did you notice the bug in your rust code? (hint: input is not supposed to be mutable)

- How fast is it? On my 2014 i7 macbook air, the supplied us averages 232 msec for 100k cycles. My version

    us:{$[x~(z;y);,"_";y]}[(*K;,"*")]':
is faster: 126msec for 100k cycles.

- How quickly was it written? I can't speak for sa/atw on us since I didn't see them write it. I wrote mine in about 30 seconds including testing. Yes really. How long did your rust program take to write? Did you think simply because you thought you understood the requirements that you didn't need to test it?

- How quickly can someone familiar with the language read it? Again, I can't speak for anyone other than myself, but I'm not a k expert -- I program in it very infrequently, and the new amend-syntax in k9 I had not run across previously. And yet I read it as quickly as I said.

These four values (less code, fast run, fast write, fast read) are the biggest most important things to me. And anyone who shows me all four of things will get my attention.

Rust? Simply does not impress.

> Unreadable array languages are popular with quants because they work in a highly competitive, cut-throat industry where "write only" languages provide job security.

That's another interesting opinion. This one might even be true amongst some quants (Most of the ones I know that use k don't particularly like k). But I suggest you try not to expect the worst in people. Yes, some people are assholes, but most people aren't. And for what it's worth, I'm not a quant (I work in Advertising).

> 126msec for 100k cycles.

Or to put it another way: 1,200 nanoseconds. That's about 3,000-5,000 instructions on a modern CPU. Believe it or not, that's actually pretty bad.

After jumping through some hoops to ensure that rustc doesn't just compile the whole thing down to a constant, I benchmarked my version as taking 15-20 nanoseconds per iteration. About 45-80 instructions!

I actually couldn't quite believe it myself, so I jumped through more hoops to ensure that it wasn't being optimised away, wasn't getting inlined too aggressively, etc... No change.

Ran it through Godbolt to inspect the assembly, and then I realised that, yes, modern languages, compilers, and CPUs really are this good!

Think about it: For the specific input example with 4 strings the algorithm boils down to: compare 7 bytes with 7 bytes, replace a pointer with another pointer, then compare 2 bytes with 2 bytes three times. That's about a hundred assembly instructions, or thereabouts.

Godbolt output:

    push   rbp
    push   r15
    push   r14
    push   r13
    push   r12
    push   rbx
    push   rax
    mov    r12,rdi
    mov    ebx,0x8
    xor    ebp,ebp
    lea    r14,[rip+0x3cb3c]        # 444e8 <anon.6527da4acb4810bb73692fa85a2e25ef.0.llvm.5506334730328533235+0x20>
    mov    r15,QWORD PTR [rip+0x3f3b5]        # 46d68 <bcmp@GLIBC_2.2.5>
    cs nop WORD PTR [rax+rax*1+0x0]
    nop    DWORD PTR [rax]
    cmp    rbp,0x2
    je     79f2 <example::testabc+0x62>
    mov    r13,rbp
    mov    rdx,QWORD PTR [rbx+r14*1]
    cmp    rdx,QWORD PTR [r12+rbx*1]
    jne    79ec <example::testabc+0x5c>
    lea    rbp,[r13+0x1]
    mov    rsi,QWORD PTR [r12+rbx*1-0x8]
    mov    rdi,QWORD PTR [rbx+r14*1-0x8]
    call   r15
    add    rbx,0x10
    test   eax,eax
    je     79c0 <example::testabc+0x30>
    cmp    r13,0x2
    jb     7a07 <example::testabc+0x77>
    lea    rax,[rip+0x3060e]        # 38007 <_fini+0xd13>
    mov    QWORD PTR [r12+0x10],rax
    mov    QWORD PTR [r12+0x18],0x1
    xor    ebx,ebx
    xor    ebp,ebp
    nop    DWORD PTR [rax+rax*1+0x0]
    cmp    rbp,0x2
    je     7a43 <example::testabc+0xb3>
    mov    r13,rbp
    mov    rdx,QWORD PTR [rbx+r14*1+0x8]
    cmp    rdx,QWORD PTR [r12+rbx*1+0x18]
    jne    7a3d <example::testabc+0xad>
    lea    rbp,[r13+0x1]
    mov    rsi,QWORD PTR [r12+rbx*1+0x10]
    mov    rdi,QWORD PTR [rbx+r14*1]
    call   r15
    add    rbx,0x10
    test   eax,eax
    je     7a10 <example::testabc+0x80>
    cmp    r13,0x2
    jb     7a58 <example::testabc+0xc8>
    lea    rax,[rip+0x305bd]        # 38007 <_fini+0xd13>
    mov    QWORD PTR [r12+0x20],rax
    mov    QWORD PTR [r12+0x28],0x1
    mov    ebx,0x8
    xor    ebp,ebp
    nop
    cmp    rbp,0x2
    je     7a93 <example::testabc+0x103>
    mov    r13,rbp
    mov    rdx,QWORD PTR [rbx+r14*1]
    cmp    rdx,QWORD PTR [r12+rbx*1+0x20]
    jne    7a8d <example::testabc+0xfd>
    lea    rbp,[r13+0x1]
    mov    rsi,QWORD PTR [r12+rbx*1+0x18]
    mov    rdi,QWORD PTR [rbx+r14*1-0x8]
    call   r15
    add    rbx,0x10
    test   eax,eax
    je     7a60 <example::testabc+0xd0>
    cmp    r13,0x2
    jb     7aa8 <example::testabc+0x118>
    lea    rax,[rip+0x3056d]        # 38007 <_fini+0xd13>
    mov    QWORD PTR [r12+0x30],rax
    mov    QWORD PTR [r12+0x38],0x1
    add    rsp,0x8
    pop    rbx
    pop    r12
    pop    r13
    pop    r14
    pop    r15
    pop    rbp
    ret    
    nop    WORD PTR [rax+rax*1+0x0]
Rust? It simply impresses.
Because you're wrong. Do spend six months doing hobby array programming. Your view will change.
It's on my bucket list. But it seems so niche that I suspect I'll never use it.

My only motivation is that I've been tempted to develop a toy programming language myself that is as high-level as Rust or C++ but explicitly designed for "wide" processing platforms such as GPUs or many-core processors with SIMD instruction sets.

All I'm saying is that the concept of array programming -- like pure functional programming -- may be valuable, but the syntax is not.

TXR Lisp:

  1> (defun rewrite (fun list)
      (build
        (while* list
          (let ((nlist [fun list]))
            (if (eq list nlist)
              (if list (add (pop list)))
              (set list nlist))))))
  rewrite
  2> (defmacro rewrite-case (sym list . cases)
      ^(rewrite (lambda (,sym)
                  (match-case ,sym
                    ,*cases))
                ,list))
  rewrite-case
  3> (rewrite-case x '(foo bar * select * fox select * bravo)
       ((select * . @rest) ^(select _ . ,rest))
       (@else else))
   (foo bar * select _ fox select _ bravo)
rewrite-case and rewrite appear in the TXR Lisp internals; they are used in the compiler for scanning instruction sequences for patterns and rewriting them.

E.g. a function early-peephole looks for one particular four instruction pattern (six items, when the labels are included). Rewriting it to a different form helps it disappear later on.

  (defun early-peephole (code)
    (rewrite-case insns code
      (((mov (t @t1) (d @d1))
        (jmp @lab2)
        @(symbolp @lab1)
        (mov (t @t1) (t 0))
        @lab2
        (ifq (t @t1) (t 0) @lab3)
        . @rest)
      ^((mov (t ,t1) (d ,d1))
        (jmp ,lab3)
        ,lab1
        (mov (t ,t1) (t 0))
        ,lab2
        ,*rest))
      (@else else)))
This is much more general and powerful than a hack which just looks at successive pairs for an ad-hoc match. rewrite-case can have multiple clauses, of different lengths, and arbitrary matching complexity.
The original requirements should be addressed. The thing being matched is not just select, but actually any one of a set of symbols that appear in K.

We can stick that data into the pattern matching syntax using the or operator:

  3> (rewrite-case x '(foo bar * select * fox where * bravo)
       ((@(or select distinct partition from where
              group having order limit) * . @rest) ^(,(car x) _ . ,rest))
       (@else else))
  (foo bar * select _ fox where _ bravo)
Or put it into a variable:

  4> (defvarl K '(select distinct partition from where group having order limit))
  K
  5> (rewrite-case x '(foo bar * select * fox where * bravo)
       ((@(member @sym K) * . @rest) ^(,sym _ . ,rest))
       (@else else))
  (foo bar * select _ fox where _ bravo)
"If an object sym which is a member of K is followed by * and some remaining material, replace that by sym, underscore and that remaining material."

Hash table:

  6> (set K (hash-list '(select distinct partition from where group having order limit)))
  #H(() (select select) (where where) (limit limit) (order order)
     (having having) (distinct distinct) (partition partition) (group group)
     (from from))
  7> (rewrite-case x '(foo bar * select * fox where * bravo)
       ((@[K @sym] * . @rest) ^(,sym _ . ,rest))
       (@else else))
  (foo bar * select _ fox where _ bravo)
This code golfs moderately well: https://news.ycombinator.com/item?id=27227276
Wow. That's some explanation. Thanks.

Do you use "rainbow brackets"?

This example is first hit I found: https://kristofferc.github.io/OhMyREPL.jl/latest/features/ra...

Such an obvious idea once you see it. Wish I had syntax coloring and rainbow brackets when I coded LISP for hire.

> Wow. That's some explanation. Thanks.

Happy to help.

> Do you use "rainbow brackets"?

No. I flash brackets, but I tend to turn off other forms of syntax highlighting. I find it extremely distracting when the syntax highlighter "decides" wrong, and I've become convinced comments at the end of lines like //} to "fix" the highlighter deal with complicated stuff is hurting more than it's helping.

In J language single sided brackets are functions/verbs
> I have to look up range/xrange and len and memorise

All you're communicating here is that you don't regularly work with Python.

The claim that you have to look up len seems disingenuous; I might believe it if you didn't look like a speaker of English.

> & means where

And that's something any engineer would know, unlike having to look up what len means?

> The claim that you have to look up len seems disingenuous

In what way?

I have to look up "how do I get the indices of a list" to get range(1,len(x)) -- I think I could have also used enumerate() and a bunch of other things, but this seemed the shortest.

> All you're communicating here is that you don't regularly work with Python.

I hope I'm communicating more than that because I put a lot of effort into my comment. I don't regularly work with k either.

What exactly do you think you are communicating?

What I mean by that is that len is an obvious abbreviation for length that crops up in numerous languages. Not to mention code bases. It's simply not plausible you could forget what len means in Python after confirming one time that it is exactly what it looks like. (Unless you're a struggling non-native speaker of English forgetting the word length itself.)
Let's do this using the same approach, "find indices and assign over them in a copy of the sequence":

  This is the TXR Lisp interactive listener of TXR 259.
  Quit with :quit or Ctrl-D on an empty line. Ctrl-X ? for cheatsheet.
  Do not operate heavy equipment or motor vehicles while using TXR.
  1> (defun subst-select-* (list)
        (let ((indices (where (op starts-with '(select *))
                              (cons nil (conses list)))))
          (if indices
            (let ((list (copy list)))
              (set [list indices] (repeat '(_)))
              list)
            list)))
  subst-select-*
  2> (subst-select-* '(foo bar * select * fox select * bravo))
  (foo bar * select _ fox select _ bravo)
(conses list) gives us a list of the list's conses: e.g in (1 2 3) the conses are (1 2 3), (2 3) and (3), so the list of them is ((1 2 3) (2 3) (3)).

where applies a function to a sequence, and returns the 0-based indices of where the function yields true.

(op starts-with '(select *)) yields a lambda which tests whether its argument starts with (select *). No brainer.

If we naively applied that to the conses, we would get thew rong indices: the indices of the select symbols, not of the asterisks.

The workaround for that is (cons nil (conses list)): we cons an extra dummy nil element to shift the positions, and process the resulting list.

Once we have the indices list, if it isn't empty, we copy the original input, and assign underscore symbols into the indicated positions. To do that we generate an infinite lazy list of underscores; the assignment takes elements from this list and puts them into the specified index positions.

If this were me, and I needed a function like us, I would have written this:

    us:{$[x~(z;y);,"_";y]}[(*K;,"*")]':
I would be interested in seeing anything that was shorter[1] and faster than that in any language, and I would be very curious to learn from anyone who could also do that faster than me.

But I'm not a fetishist: I didn't learn k because it was cute, and I don't wake up every day looking for ways to rewrite other people's code so that it is slower and bigger. Do you? Or is today special?

[1]: Measured in source-code bytes.

> Do you? Or is today special?

Today is the usual. He does that in all threads related to array languages.

> I would be interested in seeing anything that was shorter

One way to do that would be to pose that as a problem on the Code Golf Stackexchange.

My rewrite-case solution condenses (by removal of all non-essential spaces) to 69 bytes if the symbols are in a hash table K, and the input list is in a variable y, rather than a big literal:

  (rewrite-case x y((@[K @sym]* . @rest)^(,sym _ . ,rest))(@else else))
A one-letter name could be chosen for the macro. Furthermore, one-letter names could be chosen for sym, rest and else:

  20> (r x y((@[K @s] * . @r)^(,s _ . ,r))(@e e))
  (foo bar * select _ fox where _ bravo)
Still working! Now down to 43 bytes.

(It's not because I cannot that I do not do this with all my code.)

Doh, why use . ,r in the backquote if we are golfing? That should be ,*r: using the splice operator, like Common Lisp's or Scheme's ,@, getting us to 42 bytes:

  20> (r x y((@[K @s] * . @r)^(,s _ ,*r))(@e e))
  (foo bar * select _ fox where _ bravo)
I suspect Code Golf Stackechange regulars could get it down to way in one of the dedicated golfing languages like Retina or what have you.*

What else can we do? The @[K @s] predicate syntax could be replaced by a custom operator defined by defmatch, looking like @(K s).

  21> (defmatch k (sym) ^@[K (sys:var ,sym)])
  k
  22> (r x y((@(k s) * . @r)^(,s _ ,*r))(@e e)) ;; 41
  (foo bar * select _ fox where _ bravo)
Just noticed the ) * is not fully golfed:

  23> (r x y((@(k s)* . @r)^(,s _ ,*r))(@e e)) ;; 40
  (foo bar * select _ fox where _ bravo)
The k pattern operator macro could be non-hygienic: it could implicitly bind a variable called s:

  24> (defmatch k () ^@[K @s])
  k
  25> (r x y((@(k)* . @r)^(,s _ ,*r))(@e e)) ;; 38
  (foo bar * select _ fox where _ bravo)
These last few feel like cheating because they are too special purpose. Defining anything you can only possibly use just once isn't making the overall program smaller.
okay so it's not really smaller because it uses UTF8 also it relies on order of operations to get rid of the parens around ⍵≡'*' but

us←{('_'@(1+⍸2{(⍺≡⊃K)∧⍵≡'*'}/⍵))⍵}

is one(1) character shorter.

Why do you pass in (*K;,"*") as x instead of hard-coding it in x~(z;y)? Clarity?
Is the open source version (Kona) any good? Or do we have to go proprietary to try this out?
I had much fun learning the klong language: https://t3x.org/klong/

I even bought the well written book, which was a pleasure to read.

ngn-k is a different dialect from Kona, but actively-maintained and open source: https://codeberg.org/ngn/k

oK (which is also k6) can be tried here: http://johnearnest.github.io/ok/index.html

I bet Chinese and Japanese look like that to someone who knows only English too.
As some one who only knows English. No that is not what Chinese and Japanese look like to me.

That's why I prefer APL and its special symbols. It's still utterly inscrutable if you don't know it but at it looks intentional. Those symbols shift your mindset or reframe what you're looking at.

it's extremely readable when used to it, though. I use J for exploratory data analysis. It's really, really good at that kind of thing.
I've lost count of the number of times I've heard some theoretical mathematician say that about impenetrable gibberish.
"Please don't post shallow dismissals, especially of other people's work. A good critical comment teaches us something."

Dismissing what you don't understand because it is unfamiliar is the essence of a shallow dismissal, no? And you did it twice in this thread. The first was a blessing in disguise because of geocar's excellent reply, but now you're just repeating it, with added name-calling. Please don't do that here.

https://news.ycombinator.com/newsguidelines.html

It's the essence of the thing in this case, and isn't a shallow criticism.

Syntax can be good or bad. I don't believe that it's just a matter of "getting used to it", there are objective metrics of readability, developer error rates, and speed of training that some languages do poorly at.

My point was that most array languages look like line noise. That's flippant, but true.

Ask yourself this question: Is it possible to develop an array-based language and use more "normal" syntax?

Of course it is! That was a rhetorical question.

Similarly, there are other branches of science or mathematics that through an accident of history have developed impenetrable syntax.

This is not dismissing something I don't understand. I understand several such branches of mathematics and still think the syntax is unnecessarily obtuse. Coughcategory theorycough.

PS: Geocar wrote about two pages to explain what is a trivial piece of code, and I still have no idea what it even does or how! I can write code in 10 languages and read about 20-30 without too much difficulty. This includes obscure languages like Mathematica, Haskell, and several flavours of assembly language.

Array based languages are the first time I've seen a high-level language that is less readable than the machine code that they are supposed to be abstracting away! It's also the second time that I've failed to understand a simple piece of code even with a detailed explanation. For reference, the only other case is quantum algorithms.

Okay, I tell a lie. Thinking back on it, I've also seen Perl scripts that are unreadable line noise, but that's about it...

Then you have a different use case. Doesn't mean theoretical mathematicians are wrong for their use case. I do epidemiology, not pure maths though.
What part of finance uses this language? Is it in widespread use, or is like Goldman Sachs' proprietary language (I forget the name)
Various parts, particularly in markets, from pricing quants to high-frequency traders. It is fairly widespread. Barclays, JPM, UBS, Morgan Stanley, HSBC are some of the big names, then you have loads of smaller firms.
I recall a discussion on array programming languages on here a while back where someone claimed that an acquaintance of theirs was earning nearly 7 figures working on q/kdb+.
The latest language which fits quote 1 for me was Haskell. Even though I already had some functional background (Lisp), it took me seemingly forever to actually grok purely functional programming. But once it clicked, it felt like stepping up on a ladder. My perspective on other languages changed as well.
I have a common-lisp background, and I learned Haskell at the insistence of a former colleague. I also know k, and have since learned some APL and j. I would like to try and suggest to you my perspective:

The jump from Python to Haskell - or really anything along that way is like talking about a ladder of computing. You start at one end, and you are climbing upwards. And every step you take, you can look down and see all of the things you knew before, but with greater perspective.

And Haskell? Well, it's definitely pretty far up the ladder. If you get Haskell, you feel like you really understand what's going on. I know pg was talking about lisp when he was thinking blub, but in some blubish respects, Haskell is a better lisp than lisp.

But see, going from Haskell (or really anything) to Iverson is like, listen: Forget the ladder, because a ladder only goes up and down. Iverson is sideways. It is in this way, like adding depth to flatland, that Arrays are an even bigger deal than you can possibly imagine until you go there.

For me it was Prolog. I came to a class, which used Prolog, with a bad attitude of "whatever I can think of, I can program in C". Luckily for me I was schooled.
For my was Rust. I done like 12(?) Langs before, including F# that also was a change of mind, but the first 2 or 3 months of Rust I fell like an idiot looking intensely to a wall. I start to think my 20 years programming were a big fat lie.

I can't believe why it feels so hard? !I already know pascal and obj-c and F#!, kind of similar, no?

Now I feel rust so easy (as python easy!) that is weeeeeeird. (btw: I think is months now where I never think I have meet an error or situation that truly confuse me).