The actual apl implementation: +/age>17
Apl implementation of taking the length(shape) of the filtered list: ⍴(age>17)/age
(ages > 17).sum() count(ages > 17)
and storing the ages > 17 is done with is_adult = pack(ages, ages > 17)I got curious and checked this in Rust, the generated assembly is the same!
×/ product reduce
⌊/ min reduce
⌈/ max reduce
∨/ logical OR reduce (any bool set)
∧/ logical AND reduce (all bools set)
,/ catenate reduce (join without spaces)
These all show a pattern of connection clearly where the Python names don't, that they are related operations; that suggests that you could put any function on the left or any kind of array on the right and see what happens. And they work over multidimensional arrays - and you can swap / for ⌿ to reduce down columns instead of accross the rows.Your rewritten Python and JS, by showing the operation as length instead of sum, and making a shorter filtered list, hide the connection even further instead of helping to reveal and clarify it.
> "which I feel is more readable (but less efficient) than the APL inspired approach"
I know how to read it, but just look at:
age age ages age
len age for age in ages if age
what's readable about so much repetition, what's readable bout having to spot the single character plural change in the middle of 8 short words? ([>])
that's more symbols than the APL one has, the language people reject because of the heavy use of symbols(!). Why does the [] indicate loopy-listy code but so does 'for'? In PowerShell arrays have a property .Count to use instead of Length - what's readable about counting the number of ages by indirectly looking at the length of something? Is this "it's readable because I'm familiar with it" rather than "because it's objectively readable"?julia> count(age > 17 for age in ages)
Or even shorter:
julia> count(ages .> 17)
Under the hood many of the functions are implemented using the reduce function (similar to / in APL):
julia> reduce(+,ages .> 17)
I think many languages in data science have been influenced by APL. But sometimes this influence come from an intermediate language like matlab.
myfun(list)
or list.myfun()
and this applies without exception. This means a lot of C-library code gets easier to read when used from D: Vector3 vec = (Vector3){ 1, 2, 3};
Vector3 result = Vector3Add(vec, vec2);
becomes auto vec = Vector3(1, 2, 3);
auto result = vec.Vector3Add(vec2);
I know its slightly off topic, but I want more people to use D, I really prefer it to C, C++ and Rust.On its technical merits, one of the best languages out there. It gives you insane performance but is so much easier to learn and write than C++ or Rust.
from toolz import curried, pipe
pipe(ages,
curried.filter(lambda age: age > 17),
list,
len)
This way you can follow - vertically - what happens, where, and why.You can make your own pipe function to avoid 3rd party dependencies
import functools
pipe = lambda *args: functools.reduce(lambda acc, el: el(acc), args)np.sum(age>17)
(Assumes age is a np.array)
OP here; maybe I'll add a comment to the article to make the comparison between NumPy and APL for this expression. Thanks!
length $ filter (> 17) ages
And that's ignoring the fact you can also count pred = length . filter pred
count (> 17) agesselect count(*) from ages where age > 17
E: And I guess the lambda and explicit list-cast too. cf.
(count (filter #(> % 17) ages)) ages.reduce((a, c) => (a + (c > 18)), 0)
Though usually in production code you'll see this as something like ages.reduce((acc, cur) => {
return acc + c > 18 ? 1 : 0
}, 0)
Which creates some more noise but ups readability
Like this line of JS feels so much easier to read than that line of python:
Directly translating this approach to python: Although a better way to write this in python I guess would be using list comprehensions: which I feel is more readable (but less efficient) than the APL inspired approach. Overall, none of these python versions seem as readable to me as my JS one liner.