18 ms·
What learning APL taught me about Python
- hobs 3y agoThe only thing this does for me is ask why its not named count instead of sum.
- scherlock 3y agoBecause what is happening I the list of ages is being transformed into a list of booleans where it's true if the age is greater than 17. This list of booleans is then turned into a list of integers where it's 1 if true, 0 if false. This list of integers is then being summed.
- ok_dad 3y agoIn Python a Boolean true and false are often used in mathematical formulas, so they will often implicitly be coerced into the integers 1 and 0. Sum is the sum function, you can sum a sequence of numbers, but in this case it’s summing a bunch of Boolean values which are coerced to 1 and 0.
- kragen 3y agobecause sum([1, 2]) is 3 and not 2
- Jtsummers 3y agoIt is summing but being used for counting (in imitation of the same style from APL) via punning on True/False as 1/0. Not what actually happens but conceptually: ages = [17, 13, 18, 30, 12] sum(age > 17 for age in ages) => sum([False, False, True, True, False]) => sum([0, 0, 1, 1, 0]) => 2 # via conventional summing Since True and False are 1 and 0 for arithmetic in Python, this is just a regular sum which also happens to produce a count.
- naijaboiler 3y agoyeah if i ready the line using "sum", I would be expecting the result 48 (18+30) not 2
- zuminator 3y agoUsing the same format, sum((age > 17)*age for age in ages), perhaps.
- pavon 3y agonumpy (which is inspired by Matlab which is inspired by APL) does indeed have a count_nonzero function, which is intended to be used in situations like this. Unfortunately, it (like most of numpy) doesn't work with generators, just array-like objects (aka numpy arrays and python lists), so it has the same memory performance issues as filtering and using len. If your input was a numpy array to begin with you could skip the array comprehension, and shorten it to numpy.count_nonzero(ages > 17), since numpy automatically broadcasts the comparison operation to each element of the array.
- fuzztester 3y agoHere, is "broadcasts" like apply or map of functional programming?
- pavon 3y agoYes, very similar. When performing an operation between an array and a scalar, it is identical to mapping that operation on each element of the array. Broadcasting generalizes this to also handle operations between matrices and vectors, such that the operation with the vector is applied to each row or column of the matrix.
- fuzztester 3y agoThank you.
- heavyset_go 3y agofrom collections import Counter total = Counter(range(10)).total() assert total == 10
- aynyc 3y agoI know nothing about APL. But I think I would write it the same way as the OP. I also think use len is better to convey counting operation: len(age for age in ages if age > 17)
- deleted 3y ago[deleted]
- vore 3y agoI don’t think you can do that with a generator expression. You would have to write: sum(1 for age in ages if age > 17)
- Nihilartikel 3y agoIt would eat ram at scale but you could wrap the gen expression with [] for a list comprehension and that would work.
- deleted 3y ago[deleted]
- deleted 3y ago[deleted]
- aynyc 3y agoah, yes, of course, forgot the generator.
- nomel 3y agoIf you're going to go that route, I think this makes more sense: count_over_17 = [age > 17 for age in ages].count(True)
- dragonwriter 3y agoFor a very large sequence traversing it to build a list and then traversing the list to do something you could do in one traversal without creating a list may be undesirable.
- ok_dad 3y agoI find that the more language you learn the better you can utilize all of them. Also, Python is a wonderful functional language when used functionally.
- agumonkey 3y agoIt really is a strong lesson. Every language will shift and twist your mind and expand your horizon. You might hate your colleagues then though.
- heavyset_go 3y agoPython's lack of multi-line anonymous functions is a hindrance to using it as a functional language, IMO.
- dragonwriter 3y agoMost functional languages don't have statements at all, and Python's anonymous functions can, as most, handle any single expression, regardless of complexity or size. Python having a statement heavy syntax and making complex expressions (while possible) awkward is the problem with its anonymous functions, not the fact that its anonymous functions are limited to a single expression.
- heavyset_go 3y agoDefinitely, I'm looking at this from a superficial level of writing functional programming inspired code in imperative languages. JS makes this a bit more comfortable with its style of anonymous functions.
- nomel 3y agoI take the Beyonce approach to functions: if you like it you should have put a name on it.
- heavyset_go 3y agoI generally agree with this rule, but there are rare occasions where it can get awkward if you're passing functions around, like with callbacks.
- tcoff91 3y agoI 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.
- jasonwatkinspdx 3y agoYears ago I stumbled across http://nsl.com/papers/kisntlisp.htm http://nsl.com/papers/kisntlisp.htm which is similar in sentiment. I think APL's ability to lift loop patterns into tensor patterns is interesting. It certainly results in a lot less syntax related to binding single values in an inner loop.
- max_ 3y agoKenneth E Iverson, the inventor of APL was truly a genius and his primary mission was about how to bridge the world of computing and mathematics. To do this he invented the APL notation. If you find the article interesting, you might enjoy my curation of his work "Math For The Layman" [0] where he introduces several math topics using this "Iversonian" thinking. [1] Look this up to install the J interpreter. [0]: https://asindu.xyz/math-for-the-lay-man/ https://asindu.xyz/math-for-the-lay-man/ [1]: https://code.jsoftware.com/wiki/System/Installation/J9.4/Zips https://code.jsoftware.com/wiki/System/Installation/J9.4/Zip...
- gtani 3y agoThanks ex-APL2 coder here, will look up For Layman site later. My favorite APL story was somebody from morning yoga practice who grew up close to Yorktown Heights, in high school, his parents got him a job writing code for Iverson, which he described as a lot of fun. It could have been a good career choice at one point, given how many FTE's were devoted to the language at Merrill Lynch, Morgan Stanley, Lehman etc but I took the other fork
- nbelaf 3y ago[flagged]
- gorgoiler 3y agoThis is completely off topic (though possibly still on the topic of maximal readability) but the correct way to express this logic is as follows: age >= 18 If your code is specifically about the magical age of adulthood then it ought to include that age as a literal, somewhere. It becomes more obvious when you consider replacing the inline literal with a named constant: CHILD_UPTO = 17 # awkward compared with: ADULT = 18 # oh the clarity My fellow turd polishers and I would probably also add a tiny type: Age = int ADULT: Age = 18 # mwah! (The article was a good read, btw.)
- narrator 3y agoAPL makes a lot of sense in the era of 110 baud teletypes in which it was invented. Brevity was of extreme importance in that era.
- jodrellblank 3y agoAnd how many Baud do you type at today?
- narrator 3y agoI've gotten paid for working with APL code. Math professors who aren't great at typing love that stuff, but code has to be maintained and some mild verbosity, as python has, is a very reasonable price to pay for that maintainability. If this was punch card input, or 110 baud teletypes, where program listings come back at a snails pace and use paper, then APL is great for that.
- jodrellblank 3y ago> "If this was punch card input, or 110 baud teletypes, where program listings come back at a snails pace and use paper" So if your typing speed hasn't gone up in proportion to the increase in Baud, I'm guessing your reading speed also hasn't gone up tens of thousands of times, and your ability to hold working state in your head hasn't gone up thousands of times, what is the advantage of increased Baud to code readability which you are talking about? Let's say I'm not disagreeing, but I'm trying to dig into what specifically the change is which makes the difference; the computer can display more code at you per second than 1950 but humans can't read much faster than 1950 so that doesn't seem like it will help. Presumably longer books aren't inherently more readable than shorter books? Can it be that Python is more readable because it lets you skim over and not read more of the code? Since not-reading isn't reading, it seems like 'more filler' that you don't read isn't what adds to readability. It presumably isn't that Python is more English-y because languages which tend towards English words (SQL, Objective-C, PowerShell Cmdlets, Applescript, BASIC) are often maligned specifically for that reason, and because Python isn't English - you couldn't speak it to Shakespeare and have him understand you). It presumably isn't because Python uses fewer symbols, or we'd all love to write Java style var1.Equals(var2) instead of == and var1.Plus(var2) instead of + and people seem to dislike that also. Why would + be preferred over .plus() but .sum() be preferred over +/ ? Is it that Python has more visible structure to hang understanding on? Is it that it's more like walking compared to jogging compared to sprinting, that one can sustain a lower effort 'slower read' for longer?
- fsckboy 3y ago> Another big thing that APL made me realise is that the Boolean values True/False and the integers 1/0 are tightly connected Amen! It's of course also a C language tenet, and a great one. Life is so much simpler and more flexible when true and false are 1 and 0. It drives me crazy when I need to use a language where the logical operators only work on bools and the arithmetic only on ints, or some coercions work and others don't. When I incorporate somebody else's code into mine, first thing I get rid of is anything called "bool", a completely useless type. (as a nice side effect, that frees up the bool keyword for Boolean sets, which are quite useful) a disappointment with unix is that process retval has this a bit backward, 0 is success, nonzero is failure (probably because errno does want for more bits than a singleton) but it's easily enough remedied with a ! I did love everything else about APL for the brief time I used it long ago (except the difficulty of entering the symbols)
- cirex-web 3y ago* This statement is supported by the competitive programming community.
- fsckboy 3y ago(replying to myself to add) I do use hungarian, and I do use a bool-like abstract type, and it is indicated in my type calculus, but semantically my implementation allows for mixing true=1 and false=0 with other ints and arithmetic operators. I eschew implementations that do not have the convenient semantics which I feel introduces no confusion, only convenience
- Animats 3y ago> Another big thing that APL made me realise is that the Boolean values True/False and the integers 1/0 are tightly connected Not that tightly. Which is why C and Python, which both started out with boolean values just being integers, eventually retrofitted booleans to the language. Conversions come back to bite you.
- BiteCode_dev 3y agoAnd hence the numpy API.
- noobermin 3y agoI guess I'm a real computational scientist, because that first line to me wasn't special, that's how I've always written python.
- tlocke 3y agoSomeone else has mentioned it, but I would have gone with: sum(1 for age in ages if age > 17) with the other method you're treating a boolean as an int. Weak typing.
- psd1 3y agoI agree with the improvement in readability but still like the bool/int equivalence: sum(int(age > 17) for age in ages) Every nanosecond is vital!
- tlocke 3y agoInteresting, so I did a little test: python -m timeit 'sum(1 for age in range(100000) if age > 17)' 50 loops, best of 5: 5.08 msec per loop python -m timeit 'sum(int(age > 17) for age in range(100000))' 50 loops, best of 5: 7.96 msec per loop python -m timeit 'sum(age > 17 for age in range(100000))' 50 loops, best of 5: 4.78 msec per loop
- masklinn 3y agoYep, in python function calls are not cheap, you’re usually better off avoiding them in tight loops. Plus here on each iteration `int` has to be loaded from the globals before it can be called.
- palotasb 3y agobool is explicitly documented to be a subclass of int [1][2], so while it might be an obscure feature, or subjectively not someone's preferred style, I don't see any typing related issue. In general I don't think that treating an object as if it were an instance of one of its base classes is weak typing. [1]: https://docs.python.org/3/library/functions.html?highlight=subclass#bool https://docs.python.org/3/library/functions.html?highlight=s... [2]: https://docs.python.org/3/library/stdtypes.html#boolean-values https://docs.python.org/3/library/stdtypes.html#boolean-valu...
- tlocke 3y ago
- nmlhavw 3y agoWhat is it about Python? A pretty trivial feature that has been known for 30 years (and is present in NumPy) is made into a whole blog post, linked to APL to sound more interesting and is on the front page for a day. If the allegedly "most popular language" (confirmed by Gartner and Netcraft) needs that much proselytizing, perhaps it is artificially popular? Or has voting rings?
- wodenokoto 3y agoAs a beginner I definitely thought list comprehensions were easier than apply/filter style of operations. They amount to the same, but the explicit loop was much easier for me to understand (and I’m still not sure if one applies a function to a value or a value to a function, so I never remember if the function or the values go first in an apply filter call)
- RojerGS 3y agoArticle OP here! Thanks everyone for reading and for your feedback. If you have suggestions for improvements for the article, let me know here! Thanks again.
- AugustoCAS 3y agoThe author of this blogpost presented it a couple of days ago on Functional Programming Sweden: How APL made me a better Python developer by Rodrigo Girão Serrão: https://www.youtube.com/watch?v=tDy-to9fgaw https://www.youtube.com/watch?v=tDy-to9fgaw