Libraries in other languages deal with arrays just fine, and can solve actual problems at internet scale. Like numpy/jax, or pytorch, which will compile your code to clusters of TPU/GPUs. Most of the useful primitives of APL are present in these libraries (iota, reshape, reduce, etc[1]). The function composition primitives in APL are fine from a theoretical viewpoint, but I can't help feeling like I'm dealing with some obfuscation language crafted by academia to protect some fiefdom. Is it really that interesting to have a primitive for swapping arguments? Isn't that in the domain of conventions? APL was used by algorithmic trading companies for interviewing; just like Google wants you to go through Leetcode only to never use that kind of programming again.
Also APL/BQN/I/J/K all lack an auto-differentiation primitive, which is sort of a shame for 'functional' languages. Functions are mathematical objects too!
But if you just want a code golf session, then I guess APL is just fine. It also may improve your functional abstraction thinking.
APL was not designed to protect some fiefdom. It was just the notation they used on the blackboard during lectures and for that purpose it's fine. Then someone implemented it.
And later array languages have mostly abandonned the crazy names in favour of actual words, at least as far as I've seen
Agree with all your points, but I guess one advantage I can see to being more strictly "array purist" is that it forces one to write the whole program in such a way that SIMD & GPU etc optimizations would apply. These days dealing with things in array-sized chunks and avoiding excessive branching has huge advantages, performance wise, but most languages (especially OO languages) herd you into conditional branches on individual scalars, all over the place.
Obviously not applicable for the vast majority of applications, but something to think about: that it would be nice to see array-orientation further up the stack than just "compute this bulk data set on the GPU/TPU."
The lack of an AD primitive is something I've discussed with the creator of BQN, coming from a JAX world I really miss it and feel that it's such an obvious feature, especially in a language which has a way to turn a tacit function into its AST[1], which has been used for symbolic differentiation[2]. Going from symbolic to reverse-mode AD is not much of a leap and users can define their own primitives with ReBQN[3].
I see what you mean by obfuscation, but I think that it's one of those things that feels really hard and stupid until you start being able to do it really quickly. When you learn a foreign language, you first read letters, then words, then sentences because you become accustomed to larger pieces of the language that you can predict what's coming next without reading it. A similar sort of thing happens with APL/BQN, you read letters (primitives), then you begin to recognise words (small, commonly used groups of primitives), then you see larger patterns which look like magical incantations to an inexperienced user.
These "words" are (typically) tacit phrases, many of them only existing due to specific primitives like swap. Once I used BQN to golf, I started wishing Julia had a swap for operators i.e.
-(3, 5) = -2
swap(-)(3, 5) = 2
I won't defend these languages to the death, but they are fun to puzzle your brain with in codegolf. Maybe Dex[4] will go somewhere too.
> I started wishing Julia had a swap for operators
You can define one pretty easily:
julia> swap2(f::F) where F<:Function = (a, b) -> f(b, a)
swap2 (generic function with 1 method)
julia> swap2(-)(3, 5)
2
(Perhaps you meant you wished it was pre-defined with the language, I understand the slight friction of having to define things for every project; this is just making sure you know it can be done pretty easily.)
I don't know a bit of APL, but I see numbers 3 and 4 and suspiciously looking pair of triples of -1, 0 and 1.
Thus, this program of yours has game of life step function somewhere there.
The "writeonlyness" is in the eyes of beholder, and I am almost not joking - we see better what we expect to see. It is possible to determine algorithms by their structure which rarely changes much betweend different languages.
But, I have 34 years of experience in software development and I often delve into very strange languages out of curiosity and to find what does unify them or make them special for some particular tasks.
More code is "write only" than we'd care to admit. And most code that embraces that winds up more stable than you'd expect.
Note that this does not mean that the code is irreplaceable. It just means that you are almost forced to care only about two things: the overall input, and the overall output of the code in question.
This "focus on the input/output" extreme is embraced, at large. We often take it to scatter your program into a ton of easy parts where you can do this with relatively little effort at read/write time. But, zoom out and all of those little parts are almost always just as hard to work with as some of the dense "write only" code that people love to complain about.
Well written code is a dream to work with, pretty much period? Such that, I don't really disagree; but this general thought exercise is dominated by people talking past each other.
Don't get me wrong, I am not aiming for the entire program to be line noise. I've seen more attempts to make the command line argument parsing "clear and easy to read" such that "no mistakes could possibly creep in" than I'd care to admit, though.
Probably the best example of what I mean here, though, is summed up in the quote from https://gigamonkeys.com/book/a-few-format-recipes.html regarding FORMAT. "While FORMAT's control strings can be cryptic, at least a single FORMAT expression doesn't clutter things up too badly."
I didn't provide evidence because I didn't have any except from my experience which I ask you to take at my word, and I'll take you on your word that's something I'm unaware of what can happen to undo the value of well structured code in a way I've not encountered.
Your example is something I understand (though I'm not a Lisp programmer) but I'm certainly not allergic to a few judicious DSLs to clean things up.
Without evidence on either side there's no point continuing this but it was a nice answer, thanks. I wish we could discuss this in person.
I doubt we are in heavy disagreement. My point was not meant for whole programs to be done in this way. I am a heavy advocate for judicious use of the likes of XPath (or JMESPath or JSONPath/whatever) at the border to deconstruct things. Similarly, having a query language instead of manually learning how to do all of the necessary merge and sort operations for many things makes a ton of sense.
Does this mean I think DSLs are a panacea? No. Not at all. And I meant my point to be that we are in agreement that well written code is better in pretty much all cases. Be it well factored, or well whatever.
Would indeed be a fun thing for more conversations. I am always amused and amazed at how many strawman programs people have had the misfortune of working with.
He promotes a language as a better language to work with combinators, but instead of defining a way to create combinators that suits my problem, the language just offer a wider nomenclature of predefined combinators.
This is not a good approach, because you cannot fathom what your users would need.
What if I want to parse VHDL using parsing combinators? Using arrays to put it into a GPU for speed. Will I be able to define what I need to reduce boilerplate? For example, a named block parsing, because I can label my "if ... generate" operation and end a block with "end [generate] [<label name>];"
If you don't know VHDL, parse C++. Even after preprocessing it is an utmost fun. Both languages are context-sensitive to some degree and that makes their hahdling using regular CF techniques harder than it might be.
Thus far, at about third of the presentation, I am not impressed with general attitude. But history bits are very valuable, for which I am very thankful.
He put Haskell outside of array languages subset, disregarding nested data parallelism and libraries such as repa [1]. There is also no mention of NESL [2], which is an (nested) array language and more.
> He put Haskell outside of array languages subset
And as a Haskell programmer I’d argue that’s quite correct. For me, the key distinguishing feature of the array paradigm is that all operations are systematically broadcast over array operands; Haskell doesn’t have that, notwithstanding libraries such as repa which have some support for similar patterns. For a similar case, no-one’s claiming that Python is an array language, just because numpy exists.
Let me quote [1]: "An array language is a programming language that supports the manipulation of entire arrays – or portions of arrays – as a basic unit of operation."
Does Haskel support broadcast things without libraries/extensions?
That you can abstract broadcasting operations seems a silly argument. Of course you can write a program that will do the same things in both. But in neither Haskel nor Python is `[1, 2, 3] * 4` a valid statement without a lot of work. (Well, to be fair, it is valid in Python, but not in the expected sense for what we are discussing here...)
In Haskell you need to define a type and implement two classes (IsList and Num). Then you will have the ability to use [1,2,3] * 4 and get your [4,8,12].
Is it a lot of work? It does not seem like that for me.
> "An array language is a programming language that supports the manipulation of entire arrays – or portions of arrays – as a basic unit of operation."
I agree with this definition. In neither Haskell nor Python does this count as a ‘basic unit of operation’. As the sibling comment mentions, it requires a lot of extra work in both languages — hardly ‘basic’!
> Let us look at Wikipedia [2], where Ada is considered an array language.
As for this, it appears from the manual that arithmetic operators are overloaded only for 1D vectors and 2D matrices. So in this regard Ada is much more limited than ‘real’ array languages.
>As for this, it appears from the manual that arithmetic operators are overloaded only for 1D vectors and 2D matrices. So in this regard Ada is much more limited than ‘real’ array languages.
Yes, yet it is included into list. Whereas Haskell, which can have operators for arbitrary tensors, is not.
In fact, even NESL [1] is not included and I bet you will also resist to include it as an array language.
26 comments
[ 2.9 ms ] story [ 309 ms ] threadAlso APL/BQN/I/J/K all lack an auto-differentiation primitive, which is sort of a shame for 'functional' languages. Functions are mathematical objects too!
But if you just want a code golf session, then I guess APL is just fine. It also may improve your functional abstraction thinking.
/yelling at cloud, I'll go back to my cavern now
[1] https://jax.readthedocs.io/en/latest/jax.lax.html
And later array languages have mostly abandonned the crazy names in favour of actual words, at least as far as I've seen
Sharing this without comments:
https://github.com/KxSystems/kdb/blob/master/c/c/k.h
Obviously not applicable for the vast majority of applications, but something to think about: that it would be nice to see array-orientation further up the stack than just "compute this bulk data set on the GPU/TPU."
I see what you mean by obfuscation, but I think that it's one of those things that feels really hard and stupid until you start being able to do it really quickly. When you learn a foreign language, you first read letters, then words, then sentences because you become accustomed to larger pieces of the language that you can predict what's coming next without reading it. A similar sort of thing happens with APL/BQN, you read letters (primitives), then you begin to recognise words (small, commonly used groups of primitives), then you see larger patterns which look like magical incantations to an inexperienced user.
These "words" are (typically) tacit phrases, many of them only existing due to specific primitives like swap. Once I used BQN to golf, I started wishing Julia had a swap for operators i.e.
I won't defend these languages to the death, but they are fun to puzzle your brain with in codegolf. Maybe Dex[4] will go somewhere too.[1]: https://mlochbaum.github.io/BQN/spec/system.html#operation-p...
[2]: https://saltysylvi.github.io/blog/bqn-macros.html
[3]: https://mlochbaum.github.io/BQN/doc/rebqn.html
[4]: https://github.com/google-research/dex-lang
You can define one pretty easily:
(Perhaps you meant you wished it was pre-defined with the language, I understand the slight friction of having to define things for every project; this is just making sure you know it can be done pretty easily.)Thus, this program of yours has game of life step function somewhere there.
The "writeonlyness" is in the eyes of beholder, and I am almost not joking - we see better what we expect to see. It is possible to determine algorithms by their structure which rarely changes much betweend different languages.
But, I have 34 years of experience in software development and I often delve into very strange languages out of curiosity and to find what does unify them or make them special for some particular tasks.
Note that this does not mean that the code is irreplaceable. It just means that you are almost forced to care only about two things: the overall input, and the overall output of the code in question.
This "focus on the input/output" extreme is embraced, at large. We often take it to scatter your program into a ton of easy parts where you can do this with relatively little effort at read/write time. But, zoom out and all of those little parts are almost always just as hard to work with as some of the dense "write only" code that people love to complain about.
Don't get me wrong, I am not aiming for the entire program to be line noise. I've seen more attempts to make the command line argument parsing "clear and easy to read" such that "no mistakes could possibly creep in" than I'd care to admit, though.
Probably the best example of what I mean here, though, is summed up in the quote from https://gigamonkeys.com/book/a-few-format-recipes.html regarding FORMAT. "While FORMAT's control strings can be cryptic, at least a single FORMAT expression doesn't clutter things up too badly."
I didn't provide evidence because I didn't have any except from my experience which I ask you to take at my word, and I'll take you on your word that's something I'm unaware of what can happen to undo the value of well structured code in a way I've not encountered.
Your example is something I understand (though I'm not a Lisp programmer) but I'm certainly not allergic to a few judicious DSLs to clean things up.
Without evidence on either side there's no point continuing this but it was a nice answer, thanks. I wish we could discuss this in person.
Does this mean I think DSLs are a panacea? No. Not at all. And I meant my point to be that we are in agreement that well written code is better in pretty much all cases. Be it well factored, or well whatever.
Would indeed be a fun thing for more conversations. I am always amused and amazed at how many strawman programs people have had the misfortune of working with.
This is not a good approach, because you cannot fathom what your users would need.
What if I want to parse VHDL using parsing combinators? Using arrays to put it into a GPU for speed. Will I be able to define what I need to reduce boilerplate? For example, a named block parsing, because I can label my "if ... generate" operation and end a block with "end [generate] [<label name>];"
If you don't know VHDL, parse C++. Even after preprocessing it is an utmost fun. Both languages are context-sensitive to some degree and that makes their hahdling using regular CF techniques harder than it might be.
Thus far, at about third of the presentation, I am not impressed with general attitude. But history bits are very valuable, for which I am very thankful.
[1] https://hackage.haskell.org/package/repa
[2] https://en.wikipedia.org/wiki/NESL
And as a Haskell programmer I’d argue that’s quite correct. For me, the key distinguishing feature of the array paradigm is that all operations are systematically broadcast over array operands; Haskell doesn’t have that, notwithstanding libraries such as repa which have some support for similar patterns. For a similar case, no-one’s claiming that Python is an array language, just because numpy exists.
Let me quote [1]: "An array language is a programming language that supports the manipulation of entire arrays – or portions of arrays – as a basic unit of operation."
[1] https://link.springer.com/referenceworkentry/10.1007/978-0-3...
Let us look at Wikipedia [2], where Ada is considered an array language.
[2] https://en.wikipedia.org/wiki/Array_programming#Ada
I may cautiously conclude that your view is not common and Haskell is, indeed, an array language. As is Python, by itself and (faster) through numpy.
That you can abstract broadcasting operations seems a silly argument. Of course you can write a program that will do the same things in both. But in neither Haskel nor Python is `[1, 2, 3] * 4` a valid statement without a lot of work. (Well, to be fair, it is valid in Python, but not in the expected sense for what we are discussing here...)
In Haskell you need to define a type and implement two classes (IsList and Num). Then you will have the ability to use [1,2,3] * 4 and get your [4,8,12].
Is it a lot of work? It does not seem like that for me.
I agree with this definition. In neither Haskell nor Python does this count as a ‘basic unit of operation’. As the sibling comment mentions, it requires a lot of extra work in both languages — hardly ‘basic’!
> Let us look at Wikipedia [2], where Ada is considered an array language.
As for this, it appears from the manual that arithmetic operators are overloaded only for 1D vectors and 2D matrices. So in this regard Ada is much more limited than ‘real’ array languages.
[manual link: https://www.adaic.org/resources/add_content/standards/05rm/h...]
Yes, yet it is included into list. Whereas Haskell, which can have operators for arbitrary tensors, is not.
In fact, even NESL [1] is not included and I bet you will also resist to include it as an array language.
[1] https://en.wikipedia.org/wiki/NESL
But in case you will consider NESL an array language, please consider that Haskell has nested data parallelism (in language), inspired by NESL.