5 ms·
Interesting blog post. All of these are implemented as trivial operations in numpy, so while J or APL might be the originator of the syntax, this 'tool of thoug
by ipsum2 6y ago
Interesting blog post. All of these are implemented as trivial operations in numpy, so while J or APL might be the originator of the syntax, this 'tool of thought' is not as esoteric as the author makes it sound, and learning J if you already know numpy (or TensorFlow or PyTorch or..) isn't that remarkable.
>> np.array([1, 2, 3]) * 2
array([2, 4, 6])
>>> np.array([1, 2, 3]) * np.array([4, 5, 6])
>>> np.arange(0, 16).reshape(4, 4) + np.array([5, 5, 5, 5])
array([[ 5, 6, 7, 8],
[ 9, 10, 11, 12],
[13, 14, 15, 16],
[17, 18, 19, 20]])
- moonchild 6y agoYou've reproduced the most trivial example, which many languages make easy. I would be very interested to see another language with a rank operator, for instance. ------------------------------------------------------------------------ Challenge for you: rewrite a nontrivial program in one of those frameworks, with the following restrictions: - No iteration (including implicit iterations—map, filter; reduce is ok) - No loops whatsoever. Recursion is ok, but should be avoided wherever possible. - No explicitly named arguments; everything in pointfree style. (I know, map/filter can be implemented recursively. But compared with the equivalent constructs in apl, they're about 10× as verbose, and harder to reason about and understand. Even reduce is somewhat of a gimme.)
- ggrrhh_ta 6y agoI have want to chime in. Agree, it is great that ideas from APL have made it into the language... But there is, fortunately, a long and fruitful path still to explore... I love that J is like an alternative path to functional approaches like Haskell and how it plays in terms of providing expressive power. Still, I will not create code in J that is maintained jointly with other users from other domains (which is the case usually when you are using numpy, for example)
- TeMPOraL 6y agoI like how APL made it into Common Lisp wholesale. There's a library that compiles APL to Common Lisp, allowing you to mix APL and Lisp code, working on the same data structures with both languages. https://github.com/phantomics/april https://github.com/phantomics/april Example session I just did, copying APL code from https://aplwiki.com/wiki/John_Scholes%27_Conway%27s_Game_of_Life https://aplwiki.com/wiki/John_Scholes%27_Conway%27s_Game_of_...: CL-USER> (april:with-april-context ((:space *lifespace*)) (april:april "Show←{'·⌺'[⎕IO+⍵]}") (april:april "glider←3 3⍴1 1 1 1 0 0 0 1 0") (april:april-f "Show glider") (print (april:april "glider")) ; to prove it's a Lisp array (terpri) (april:april "grid←¯10 ¯10↑glider") (april:april-f "Show grid") (terpri) (april:april "life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +⌿ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}") (terpri) (april:april-f "Show¨{life⍣⍵⊢grid}¨0,⍳3") (terpri)) ⌺⌺⌺ ⌺·· ·⌺· #2A((1 1 1) (1 0 0) (0 1 0)) ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·······⌺⌺⌺ ·······⌺·· ········⌺· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ·········· ········⌺· ·······⌺⌺· ·······⌺⌺· ·······⌺⌺⌺ ·······⌺⌺· ·······⌺·⌺ ······⌺⌺·· ·······⌺·· ·······⌺·⌺ ·······⌺·· ········⌺· ········⌺· ·········· ·········· ·········· (APL being compiled to Lisp is important, not just because it gives you proper interoperability out of the box, but also because on implementations like SBCL this means APL code will get compiled, through Lisp, to native code. And you can do that at runtime too.) Now I wish I could find something that does the same for J, because I'm sure as hell not going to buy an APL-friendly keyboard.
- icen 6y agoEmacs has an excellent APL keyboard implementation (via gnu-apl-mode) which uses . as a prefix for the apl keys.
- moonchild 6y ago> APL being compiled to Lisp is important, not just because it gives you proper interoperability out of the box, but also because on implementations like SBCL this means APL code will get compiled, through Lisp, to native code Except that simd in lisp isn't great and needs to be done mostly by hand[1]; and simd is super important for making apl performant. Also because gc attributes that are good for lisp aren't great for apl. Also because compiling to native code doesn't actually really affect performance of apl, and might actually harm it for larger programs. To be clear, april is a super-cool project, but not because of any of its performance characteristics. > Now I wish I could find something that does the same for J, because I'm sure as hell not going to buy an APL-friendly keyboard. You don't need a special keyboard, just a layout. I don't know how it works on windows (there's an 'IME', I think?) or macos, but x comes with a keyboard layout. I use this: setxkbmap -layout us,apl -variant dyalog -option grp:rwin_switch Which makes it so that holding down the right super key and pressing any key produces an appropriate apl symbol. You may want to use a different key to switch; check 'man xkeyboard-config' and then search for 'Switching to another layout' for other options. Try: modifier key (whatever it is)+r, for ⍴ (rho). 1. https://www.reddit.com/r/lisp/comments/fkfgjn/sbcl_with_simd_how_to_optimize_sseavx2_to_pointer/ https://www.reddit.com/r/lisp/comments/fkfgjn/sbcl_with_simd...
- SuperCuber 6y agoRank Is actually implicitly done by numpy using a mechanism called broadcasting. For example: >>> np.array([10, 20, 30]) + np.array([[1,2,3], [4,5,6], [7,8,9]]) array([[11, 22, 33], [14, 25, 36], [17, 28, 39]]) Sieves exist in numpy, called masks: >>>np.array([10, 20, 30]) > 15 array([False, True, True]) Of course they can be operated on just like any other numpy array. Grades exist in numpy: >>>np.array([5,4,3,2,1]).argsort() array([4, 3, 2, 1, 0]) Of course they are a little more verbose since all of those operations are from the library and not native to python.
- ipsum2 6y agoTo do sieves in J like the blog post mentioned, the equivalent numpy would be: >>> arr = np.array([10, 20, 30]) >>> arr[arr > 15] array([20, 30])
- moonchild 6y ago> Rank Is actually implicitly done by numpy using a mechanism called broadcasting. Numpy's broadcasting is scalar conformability, to which the rank operator (and general conformability) provides a general case. Example in j: ] x =. i. 2 3 4 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ] y =. i. 3 4 0 1 2 3 4 5 6 7 8 9 10 11 x + y NB. this will error because + expects that, if its arguments' shapes are not the same, one will be a prefix of the other |length error | x +y NB. this is easy enough to fix, however x +"2 y NB. +"2 is shorthand for +"2 2; meaning, choose rank-2 arrays from both the left and right arguments 0 2 4 6 8 10 12 14 16 18 20 22 12 14 16 18 20 22 24 26 28 30 32 34 Numpy will actually do this without the rank operator, because it uses suffix agreement rather than prefix agreement (which is absolutely bonkers—j used suffix agreement for about 5 minutes in 1990, before realising it was an awful idea). For for numpy, see if you can add: np.array([[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]) + np.array([[0, 1, 2], [3, 4, 5]]) Intelligently. (I'm sure it's not overly difficult to come up with a solution, but can you do it with a single higher-order function call which generalises to other argument shapes?) (The j equivalent, (i. 2 3 4) + (i. 2 3) also works without trouble.) ------------------------------------------------------------------------ Another example, which may be more illustrative, is the ability to perform reductions along arbitrary axes. For example: ] x =. i. 4 3 0 1 2 3 4 5 6 7 8 9 10 11 +/ x NB. sum reduced along leading axis, the default, producing an array of shape 3 18 22 26 +/"1 x NB. sum each rank-1 array (vector); or, reduce last axis, producing an array of shape 4 3 12 21 30 ------------------------------------------------------------------------ Another curiosity, which I have thus far neglected, is the extent to which numpy's being 'a little more verbose' is actually incredibly important in shaping the way you approach and think about problems. The great-uncle comment also addresses this, but Iverson probably says it better than either of us can: read https://www.jsoftware.com/papers/tot.htm https://www.jsoftware.com/papers/tot.htm
- ggrrhh_ta 6y agoExactly, one of the things I like about numpy is how it drew from APL and J. It is a more verbose language, and truth be told, J still has more expressive power (non regular processing of data, even state-machine-like processing) which can still influence numpy, or Julia.
- smabie 6y agoComparing numpy to J is like comparing a dirty rag to a designer suit. The examples you've listed are trivial; the power of J and other array languages cannot be appreciated from afar. The philosophy behind array languages runs much deeper than adding two arrays or transposing matrices.
- ipsum2 6y agoCan you give an example of something that can be done in J that can't be done easily in numpy?
- sn41 6y agoIt is not about "cannot be done". Most high-level languages are Turing complete, hence every program in one can be written as some program in another. The real clincher is expressiveness: shorter code is better, modular code is better, and languages which avoid unnecessary repetitiveness of boiler plate code is better. Different languages provide advantages along one or more of these parameters.
- shawnz 6y agoSo can you give an example of something that can be expressed simply in APL/J/K that can't be expressed simply with numpy? And to be clear I don't think "less characters" makes the expression more simple. Maybe "less statements" or "less operators"
- smabie 6y agoHere's Conway's Game of Life by Arthur Whitney (creator of K): life:{3=a-x*4=a:2{+(0+':x)+1_x,0}/x} I'm guessing a numpy implementation would be between one and two orders of magnitude more verbose, even if you're just comparing the number of operations.
- worc3131 6y agoIt doesn't quite seem fair to compare numpy to one of the most famous K code golf's ever created, but here is my attempt, 140 characters compared to Arthur's 30, so 5 times longer. Not worry about padding the edges would cut it to 100. I don't think the gap is as wide as you think it is. def life(M): MP = np.pad(M,[(1,1),(1,1)]) N = sum(np.roll(MP,(i,j),(0,1)) for i in [1,0,-1] for j in [1,0,-1]) return (3==N-MP*(4==N))[1:-1,1:-1] Speedtest (presumably memory bound): 100 x 100 matrix, numpy 0.2ms, K 0.2ms 200 x 200 matrix, numpy 0.5ms, K 0.8ms 500 x 500 matrix, numpy 5.2ms, K 8.0ms 1000x1000 matrix, numpy 20.0ms, K 36.0ms 5000x5000 matrix, numpy 0.5s , K 1.2s Interestingly the K code is designed to return a boolean matrix, but operates much more slowly for me on a boolean matrix compared to an integer matrix, with the result that: q)\t klife M Takes 1.2 seconds whilst q)\t klife klife M Takes 24 seconds. So whilst the idea seems to be that klife is used with scan/over to run a number of iterations, it's actually a bad idea to run it more than once.