I know it may not be the main draw of APL, but I would love to see some performance comparisons between the elegant APL solutions in these videos, and the "best" solutions in other languages. It'd be interesting to see what (if any) tradeoffs there are, or if there are any surprising scaling effects based on the input.
Yes I'm also interested in APL performance. I know Dyalog is always improving their product and getting graphics card processors and down-to-the-metal machine code to do the crunching. Recognizing huge libraries of idioms and writing specialized code for them. If the expression is "common" they have probably already optimized it. That's something a generic language on generic platforms can't do. Well, maybe JavaScript engines enjoy such advancements since there is an ongoing battle for performance specs between browsers.
//z^3 a=[0,2,1,5,3,4]; a#a // Actual logic is just #, which is an index access operator, if operated on any array. For this problem, the answer is just one character (3 to be generous). # operator is far more powerful and is polymorphic depending on the types of arguments it operators on.
Always love seeing APL content Connor. Have you had a look at the Fin APL idioms list yet? I've had a look at it but I have a hard time digging into it. I'd love if you would make a series with them. (Perhaps a backwards format, where you start with the APL idiom and then implement the same thing in another language).
2:28, C++ solves this in 3 characters too: #define D auto rslt = arr; auto it = rslt.begin(); #define F for (auto &n: arr) *it++ = arr[n]; #define R return rslt; template auto buildArray (const T &arr) { D F R }
GNU APL doesn't support tacit (forks, trains) but the solution is really just as brief: ⎕IO←0 n ← (0 2 1 5 3 4) (⊂n)⌷n 0 1 2 4 5 3 tests ← (0 2 1 5 3 4) (5 0 1 2 3 4) buildArray ← {⊂⍵⌷⍵} buildArray ¨ tests 0 1 2 4 5 3 4 5 0 1 2 3 It's actually pretty easy to back-port Dyalog APL's forks and 2-trains to APL2 syntax [From aplwiki.com/wiki/Tacit_programming#Trains] Trains A train is a series of functions in isolation. An isolated function is either surrounded by parentheses or named. Below, ⍺ and ⍵ refer to the arguments of the train. f, g, and h are functions (which themselves can be tacit or not), and A is an array. The arguments are processed by the following rules: Forks A 3-train is a fork: (f g h) ⍵ === ( f ⍵) g ( h ⍵) ⍺ (f g h) ⍵ === (⍺ f ⍵) g (⍺ h ⍵) The left tine of a fork can be an array: (A g h) === A g ( h ⍵) ⍺ (A g h) ⍵ === A g (⍺ h ⍵) Atops A 2-train is an atop: (g h) ⍵ === g ( h ⍵) ⍺ (g h) ⍵ === g (⍺ h ⍵)
Why not nums[1 + nums] ? I learned APL decades ago, so maybe I'm not hip with some of the newer techniques. It would seem to me this is a problem that fits the fundamentals very well though. Also, this way you don't have to mess with quad-IO.
I'm quite a noob with APL but this was my first thought too. But watching his other APL videos, he often mentions point-free programming and how much he likes that style. So basically the "enclose-index-identity" fork is just a codegolfed and point-free solution
Because that could not be further from an elegant solution. It's not point free. It's not expresive, it's not taking advantage of combinators, and it has that ugly inline 1 constant. The 3 character solution might be harder to read at a glance. But it is a lot more idiomatic
@@sebastiangudino9377 > could not be further from an elegant solution It is the most elegant solution by far. It is ground-truth-basic APL, dating back to the original language I learned from the very people who created it back in the early 80's. > It's not point free. It's not expresive, it's not taking advantage of combinators Oh, please. Be careful not to confuse modern jargon and fads with getting the job done with clean, simple and BACKWARDS COMPATIBLE code. > that ugly inline 1 constant Again. Be careful about making these kinds of judgements. There's nothing "ugly" about this approach. You will find it in every APL codebase from here to the origins of the language. "Ugly"? Now we are writing code to display at the Louvre? C'mon. What is dangerous is messing with quad-IO. I made that mistake as a newbie 40 years ago. It opens the door to untold misery in real code. It's one thing to write a cute little solution to a problem. Quite another to design and support a codebase running an entire factory or a DNA sequencing system (both of which I did in APL back in the day). I don't even want to get into just how dangerous it can be to try to be "elegant" in real production code. > it is a lot more idiomatic That's very funny. How many years of production programming in APL do you have? Right.
Apart from the fact that it is (unfortunately) a 1-based array language, in Matlab/Octave, you would do nums(nums) directly (and probably the same in numpy Python, but 0-based). Why is the "horseshoe" operator necessary in APL? What does it do exactly?
@@martin42 My comment about 1-based language was about Matlab/Octave actually ;) My question is about the first glyph, I think it's called enclose? What does it do? What is the difference between (enclose nums) and just nums? In other words, why would (identity index identity) not work?
@@VincentZalzal Ah, sorry for not understanding your question. The index operator takes a scalar as the left argument. We want to pass it a vector instead. One way to do this is to "enclose" the vector into a scalar. In APL a scalar has a rank of 0, a vector 1 and a matrix 2. If you enter something like "⍴⍴5", you are asking for the rank of the number 5. The result will be 0. The rank of a vector, say, 1 2 3 4, will be 1: "⍴⍴1 2 3 4". So, the trick to be able to pass several numbers, a vector, as the left argument of the index operator, is to fist enclose it into a scalar...think of wrapping it inside a scalar. So "⍴⍴ ⊂1 2 3 4" gives you a rank of 0. Example using conventional indexing and an standard index origin of 1: "APL is not fun"[1 2 3 4 5 6 11 12 13 14] Result: "APL is fun" If you wanted to use the index operator (⌷) instead, you can't do this because the operator expects a scalar on the left: (1 2 3 4 5 6 11 12 13 14)⌷"APL is not fun" ....this will not work If you enclose the vector... (⊂1 2 3 4 5 6 11 12 13 14)⌷"APL is not fun" You get "APL is fun" again.
@@martin42 Thank you, I understand now! In Matlab/Octave/Python-numpy, the index operator can actually use rank-0 or rank-1 arguments, but in APL, it must be rank-0. And the enclose function, by reducing the rank, allows a kind of "map" function (as in functional programming), applying the index operator on each element enclosed.
Oh wow. A fork is an S’ combinator. Goooootcha. Yeah I still don’t get how APL sees 3 functions in ()s and that they’re unary binary unary. Especially when so many glyphs operate on functions themselves, like the compose (or bind) operator. Hmm! There’s clearly a dimension I am missing. Accepting the fact that arguments have to be either values or function is confusing. Or… it’s not, but realizing that I had to was. Ok. Unrelated. I think I finally understand what point free means. I don’t get the etymology but I think I can look it up now and understand it as I have enough (albeit loose) intuition.
I know it may not be the main draw of APL, but I would love to see some performance comparisons between the elegant APL solutions in these videos, and the "best" solutions in other languages. It'd be interesting to see what (if any) tradeoffs there are, or if there are any surprising scaling effects based on the input.
Yes I'm also interested in APL performance. I know Dyalog is always improving their product and getting graphics card processors and down-to-the-metal machine code to do the crunching. Recognizing huge libraries of idioms and writing specialized code for them. If the expression is "common" they have probably already optimized it. That's something a generic language on generic platforms can't do. Well, maybe JavaScript engines enjoy such advancements since there is an ongoing battle for performance specs between browsers.
You have a great way of explaining the APL syntax from the ground up! Appreciate all the work you put into your videos.
Thanks!
First time seeing APL code. I'm just as amazed as scared by it.
Also great video
In J the solution is just {~, that is, index-selfie in APL terminology (index does not need the indices enclosed in this case in J).
//z^3
a=[0,2,1,5,3,4];
a#a
// Actual logic is just #, which is an index access operator, if operated on any array. For this problem, the answer is just one character (3 to be generous). # operator is far more powerful and is polymorphic depending on the types of arguments it operators on.
Always love seeing APL content Connor.
Have you had a look at the Fin APL idioms list yet? I've had a look at it but I have a hard time digging into it.
I'd love if you would make a series with them. (Perhaps a backwards format, where you start with the APL idiom and then implement the same thing in another language).
Since the python solution is functional & can fit in a single expression. Instead of defining a function & returning, you can use lambdas
2:28, C++ solves this in 3 characters too:
#define D auto rslt = arr; auto it = rslt.begin();
#define F for (auto &n: arr) *it++ = arr[n];
#define R return rslt;
template auto buildArray (const T &arr) { D F R }
Lmao
A point-free Haskell solution would be:
buildArray = map (-1). map (!!) id . map (+1)
Where () is the S combinator when used with functions
GNU APL doesn't support tacit (forks, trains) but the solution is really just as brief:
⎕IO←0
n ← (0 2 1 5 3 4)
(⊂n)⌷n
0 1 2 4 5 3
tests ← (0 2 1 5 3 4) (5 0 1 2 3 4)
buildArray ← {⊂⍵⌷⍵}
buildArray ¨ tests
0 1 2 4 5 3 4 5 0 1 2 3
It's actually pretty easy to back-port Dyalog APL's forks and 2-trains to APL2 syntax
[From aplwiki.com/wiki/Tacit_programming#Trains]
Trains
A train is a series of functions in isolation. An isolated function is either surrounded by parentheses or named. Below, ⍺ and ⍵ refer to the arguments of the train. f, g, and h are functions (which themselves can be tacit or not), and A is an array. The arguments are processed by the following rules:
Forks
A 3-train is a fork:
(f g h) ⍵ === ( f ⍵) g ( h ⍵)
⍺ (f g h) ⍵ === (⍺ f ⍵) g (⍺ h ⍵)
The left tine of a fork can be an array:
(A g h) === A g ( h ⍵)
⍺ (A g h) ⍵ === A g (⍺ h ⍵)
Atops
A 2-train is an atop:
(g h) ⍵ === g ( h ⍵)
⍺ (g h) ⍵ === g (⍺ h ⍵)
That’s beautiful! ☺️
Why not nums[1 + nums] ?
I learned APL decades ago, so maybe I'm not hip with some of the newer techniques. It would seem to me this is a problem that fits the fundamentals very well though. Also, this way you don't have to mess with quad-IO.
I'm quite a noob with APL but this was my first thought too. But watching his other APL videos, he often mentions point-free programming and how much he likes that style. So basically the "enclose-index-identity" fork is just a codegolfed and point-free solution
Because that could not be further from an elegant solution. It's not point free. It's not expresive, it's not taking advantage of combinators, and it has that ugly inline 1 constant. The 3 character solution might be harder to read at a glance. But it is a lot more idiomatic
@@sebastiangudino9377
> could not be further from an elegant solution
It is the most elegant solution by far. It is ground-truth-basic APL, dating back to the original language I learned from the very people who created it back in the early 80's.
> It's not point free. It's not expresive, it's not taking advantage of combinators
Oh, please. Be careful not to confuse modern jargon and fads with getting the job done with clean, simple and BACKWARDS COMPATIBLE code.
> that ugly inline 1 constant
Again. Be careful about making these kinds of judgements. There's nothing "ugly" about this approach. You will find it in every APL codebase from here to the origins of the language. "Ugly"? Now we are writing code to display at the Louvre? C'mon.
What is dangerous is messing with quad-IO. I made that mistake as a newbie 40 years ago. It opens the door to untold misery in real code. It's one thing to write a cute little solution to a problem. Quite another to design and support a codebase running an entire factory or a DNA sequencing system (both of which I did in APL back in the day). I don't even want to get into just how dangerous it can be to try to be "elegant" in real production code.
> it is a lot more idiomatic
That's very funny. How many years of production programming in APL do you have? Right.
Apart from the fact that it is (unfortunately) a 1-based array language, in Matlab/Octave, you would do nums(nums) directly (and probably the same in numpy Python, but 0-based). Why is the "horseshoe" operator necessary in APL? What does it do exactly?
In APL you can change the index origin to be 0. That's the "quad IO" he refers to in the video. You'd be amazed how useful this can be.
@@martin42 My comment about 1-based language was about Matlab/Octave actually ;) My question is about the first glyph, I think it's called enclose? What does it do? What is the difference between (enclose nums) and just nums? In other words, why would (identity index identity) not work?
@@VincentZalzal Ah, sorry for not understanding your question. The index operator takes a scalar as the left argument. We want to pass it a vector instead. One way to do this is to "enclose" the vector into a scalar. In APL a scalar has a rank of 0, a vector 1 and a matrix 2. If you enter something like "⍴⍴5", you are asking for the rank of the number 5. The result will be 0. The rank of a vector, say, 1 2 3 4, will be 1: "⍴⍴1 2 3 4". So, the trick to be able to pass several numbers, a vector, as the left argument of the index operator, is to fist enclose it into a scalar...think of wrapping it inside a scalar. So "⍴⍴ ⊂1 2 3 4" gives you a rank of 0.
Example using conventional indexing and an standard index origin of 1:
"APL is not fun"[1 2 3 4 5 6 11 12 13 14]
Result: "APL is fun"
If you wanted to use the index operator (⌷) instead, you can't do this because the operator expects a scalar on the left:
(1 2 3 4 5 6 11 12 13 14)⌷"APL is not fun" ....this will not work
If you enclose the vector...
(⊂1 2 3 4 5 6 11 12 13 14)⌷"APL is not fun"
You get "APL is fun" again.
@@martin42 Thank you, I understand now! In Matlab/Octave/Python-numpy, the index operator can actually use rank-0 or rank-1 arguments, but in APL, it must be rank-0. And the enclose function, by reducing the rank, allows a kind of "map" function (as in functional programming), applying the index operator on each element enclosed.
@@martin42 You’ve made APL fun again ☝🏻😂
Oh wow. A fork is an S’ combinator. Goooootcha. Yeah I still don’t get how APL sees 3 functions in ()s and that they’re unary binary unary. Especially when so many glyphs operate on functions themselves, like the compose (or bind) operator. Hmm! There’s clearly a dimension I am missing. Accepting the fact that arguments have to be either values or function is confusing. Or… it’s not, but realizing that I had to was.
Ok. Unrelated. I think I finally understand what point free means. I don’t get the etymology but I think I can look it up now and understand it as I have enough (albeit loose) intuition.
Point-free means just that you don't mention the argument
code_report, I don't have vision problems, but I have a very difficult time reading the colors of your ipython session.
insane
Why not nums[nums]
"TypeError: list indices must be integers or slices, not list" :) it does work on numpy tho
@@ooloncolluphid9975 Sorry, mine was an APL solution.
it makes expressions like (nums,morenums)[nums,morenums] messier than (⊂⌷⊢)nums,morenums, i.e. nums only needs to be present one time
Thats also valid python if you're using a numpy array :)
BQN:
⊏˜
APL is just math with a compiler
APL ∧ Category Theory ⊃ ⌽ 'Push the Subscribe Button' ⌽