6 ms·
I feel like this kind of operation on a list feels more naturally expressed by filtering the list and taking the length of the filtered list. Like this line of
by tcoff91 3y ago
I feel like this kind of operation on a list feels more naturally expressed by filtering the list and taking the length of the filtered list.
Like this line of JS feels so much easier to read than that line of python:
ages.filter(age => age > 17).length
Directly translating this approach to python:
len(list(filter(lambda age: (age > 17), ages)))
Although a better way to write this in python I guess would be using list comprehensions:
len([age for age in ages if age > 17])
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. Obviously if the function is on a hot path iterating and summing with a number is far more efficient versus filtering. In that case i'd probably still use something like reduce instead of summing booleans because the code would be more similar to other instances where you need to process a list to produce a scalar value but need to do something more complex than simply adding.
- deleted 3y ago[deleted]
- WhiteRice 3y agoI didn’t see it in the article so I thought I would add, The actual apl implementation: +/age>17 Apl implementation of taking the length(shape) of the filtered list: ⍴(age>17)/age
- Jtsummers 3y agoIt's in there but near the end (80% or so of the way down the page). The article would benefit from moving that to the top and drawing the comparison to the APL code earlier.
- adalacelove 3y agoIf ages is a numpy array instead of a list: (ages > 17).sum()
- dTal 3y agoNumpy is something close to APL semantics with Python syntax. There's no doubt it was heavily inspired by APL. One could argue that numpy's popularity vindicates the array model pioneered by APL, while driving a nail in the coffin of "notation as a tool of thought", or APL's version of it at any rate. Array programming has never been more popular but there's no demand for APL syntax.
- adalacelove 3y agoI think the key to array programming success was due to the possibility of having fast interpreted languages for numerical computing. I'm very used to programming in array languages (Matlab, Python, Julia and even Mathematica has lot of vectorized ops) and still I think a lot in terms of for loops.
- sideshowb 3y agoThis. Ideally we'd have jitted python loops be efficient. I love array syntax for simple examples but for more complex ops a loop is often clearer.
- TheRealKing 3y agoNumpy's vectorized syntax is inspired by Fortran.
- jph00 3y agoIt feels more natural to you because of familiarity. However, if you've learned Iverson Bracket notation in math (https://en.wikipedia.org/wiki/Iverson_bracket https://en.wikipedia.org/wiki/Iverson_bracket) then the APL approach will probably feel more natural, because it's a more direct expression of the mathematical foundations. Of course, the actual APL version is by far the most natural once you're familiar with the core ideas: +/ages>17
- lmm 3y agoI felt the same way about Rust's .await and .?; I think we're gradually converging on the idea that postfix operators are the right way to do 1-argument functions. I'm not remotely convinced that RPN is a readable idea in general, but when we have a pipeline rather than a tree, writing it left-to-right is the winner.
- Bostonian 3y agoIn Fortran it's count(ages > 17) and storing the ages > 17 is done with is_adult = pack(ages, ages > 17)
- veber-alex 3y ago> Obviously if the function is on a hot path iterating and summing with a number is far more efficient versus filtering. I got curious and checked this in Rust, the generated assembly is the same! https://rust.godbolt.org/z/jhGWdYPz1 https://rust.godbolt.org/z/jhGWdYPz1
- proto_lambda 3y agoBecause you're not actually creating a filtered list, you're just filtering an iterator.
- nonameiguess 3y agoNeither of your examples is doing what the parent suggested as an alternative, which is simply looping and counting rather than creating an anonymous function to repeatedly call. I doubt this makes an enormous difference in practice, but you can see your generated assembly doing all the argument passing and stack frame setup, which is extra instructions compared to just straight looping and counting.
- jodrellblank 3y agoThe blog starts out with mentioning other reduce operations in Python: 'prod from the math module; min; max; any; all; and "".join'. In APL those are: ×/ 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"?
- kwertzzz 3y agoInterestingly most (if not all) python examples also work in julia. Instead of using sum one can also use count, which might be more 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.
- leephillips 3y agoYes. One of the “repercussions” of the author’s experience with APL might have led to him recoiling from the verbosity of his Python examples and exploring languages, like Julia, that can get closer to the APL spirit.
- lionkor 3y agoDoing @WalterBright's job for a second to let you know that in the D language, given a function that is defined as taking, say, a list as the first argument, it can be called either 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.
- tcoff91 3y agoI love D, learned it back in high school, but unfortunately it just seems like D is one of those great languages that just isn't going to take off. It just doesn't have a big enough champion to bootstrap a thriving large community that would make more people want to learn it and more companies adopt it. 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.
- deleted 3y ago[deleted]
- marko-lev 3y agoI like to leverage some FP to bring readability to python 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)
- alanbernstein 3y agoI feel like the numpy version is most concise, most readable, and most array-ish: np.sum(age>17) (Assumes age is a np.array)
- RojerGS 3y agoYes, this is VERY close to the APL way: +/ages>17 OP here; maybe I'll add a comment to the article to make the comparison between NumPy and APL for this expression. Thanks!
- leprechaun1066 3y agoAPL (along with Matlab, K, Mathematica, etc.) was one of the influences for NumPy anyway.
- drcongo 3y agoYour list comprehension is how I would naturally write this, and to me seems the most readable - it's almost like an English language sentence. The sum example in the article would probably be my second choice.
- Expurple 3y agoYes. This gets even better with Haskell's partial application: length $ filter (> 17) ages And you can also count pred = length . filter pred count (> 17) ages
- theodpHN 3y agoIMO, SQL is more straightforward if not quite as terse: select count(*) from ages where age > 17
- redpenguin101 3y ago"Easier to read" is relative. For someone used to lisp syntax, the Python looks quite natural - only the parens are in the wrong place. E: And I guess the lambda and explicit list-cast too. cf. (count (filter #(> % 17) ages))
- banashark 3y agoThe concise reduce is fairly small: 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