5 ms·
When I looked at Python vs Ruby many years ago, I found the opposite: Why does Python have (special) functions like len() and map(), instead of 'properly' suppo
by MartinMond 4y ago
When I looked at Python vs Ruby many years ago, I found the opposite: Why does Python have (special) functions like len() and map(), instead of 'properly' supporting both OOP (len should just be a method on objects) and/or FP (support multi-line lambdas so I can actually use map/filter etc).
I never understood how this can be considered consistent at all, and those IMHO language design warts made me look into Ruby at the time.
Has this improved since? I know print was changed in Python3 to make it not-special.
- duckerude 4y agomap() and filter() being functions means you can run them on any object that implements __iter__(). To make them methods they'd have to be added to each individual type that might want to use them. IIRC Ruby does that using mixins but that takes up a lot of room in the namespace of each type and makes it a little harder to figure out where the methods come from. The justification for len() is thinner: Guido van Rossum thinks it looks better, and it enforces a consistent name with a consistent meaning (you don't get length methods with different names, or with the right name but strange behavior). Under the hood it just calls obj.__len__(), so it's only the notation that's not OOP. There are no multi-line lambdas because nobody could come up with an indentation-based syntax for them that Guido van Rossum was happy with. In short: none of them improved, all of them have reasons, some of those reasons are bad.
- aidos 4y agoThose functions take any object that supports the iteration protocol. There’s no need for adding say, len, to every object that needs a len function. I’d argue that is consistent. Sure, multi line lambdas might be nice, but equally you can define functions wherever you need them so it’s not really all that different.
- ajanuary 4y ago> There’s no need for adding say, len, to every object that needs a len function. fwiw, Ruby does the same thing, but using the Enumerable mixin rather than a free floating function. > I’d argue that is consistent. I don’t understand this argument. It’s convenient and more efficient to write, yes. But how is it more consistent to have two different calling conventions? The claim that there’s one way to do something irks me as well. There’s one way to find the length of something, but that introduces two ways of querying an object. It’s a good guiding principle, but when people use it to make stronger claims, it ends up superficial.
- aidos 4y agoBy which I mean that you can consistently use `len` etc on any iterable and they will always behave in the same way. You learn them on day one and they work consistently in every place you see them forever.
- Calavar 4y ago> There’s no need for adding say, len, to every object that needs a len function. I’d argue that is consistent. Python's len works by calling the __len__ method, which must be added to every class that needs a len function. Since you're already defining a method with a standard name on every class that needs it, Ruby just has you call that method directly as opposed to the absurdity of intentionally obfuscating the method name with underscores so that people use a global helper function instead. Even C++, with all its warts and its 1980s design got this right in the std lib with the size method. Honestly, I find some of these defenses of Python quite humorous.
- jacquesm 4y agoIt's almost like they have become memes in themselves. "There is only one way to do it" -> except for those cases where there is a multitude, including the base choice of major release of interpreter, package manager, and so on. Python is very usable but it definitely isn't perfect and the only way you get to improve on stuff is to recognize its shortcomings.
- aidos 4y agoI'd agree that __len__ is a fairly trivial example, but it is used for truthiness in Python too. It seems like a reasonable choice to mark these blessed methods that the language is using deeply as part of the runtime in a special way but I'll happily concede that it could have been X.len() instead. `sorted`, `list`, `set`, are more interesting cases where they all work with the underlying __iter__ protocol. You don't also want to add X.sorted(), X.as_list(), X.as_set() etc too. Again, you could have X.as_iterable() to implement these, or you could mixin sorted, which will call the __iter__ function. But honestly, it's really neither here nor there. For the full avoidance of doubt - I'm not arguing for these other "memes" and, in particular, "There is only one way to do it" has never made that much sense to me. I am arguing that Pythons `sorted` api is reasonable, consistent and not worth worrying about.
- jacquesm 4y agoLanguage design is a hard problem. You have to make so many compromises to get it to the point where it works for a large variety of use cases that the degree of ugliness is almost directly proportional to the breadth of application and adoption. The only languages that manage to stay clean are the ones that nobody uses. I don't think that's avoidable. Mistakes made early on have a habit of compounding over time and calcification makes it harder and harder to deal with them decisively and in a non-breaking way. Python made a couple of bad decisions but on the whole the language came out relatively unscathed, most of the original design constraints are still satisfied. As opposed to say PHP or Java which ended up very far removed from where they started out. Case in point: Python's GIL must have seemed like a good idea at the time, a quick fix for an urgent problem. And now that quick fix is the albatross that we can't seem to get rid of.
- samwillis 4y agoMap/filter are considered inferior in Python to list comprehensions. res = [x**2 for x in range(10) if x != 5]
- michaelteter 4y agoBefore Ruby introduced filter_map: res = (1..10).select { |x| x != 5 }.map { |x| x ** 2 } With filter_map: res = (1..10).filter_map { |x| x ** 2 if x != 5 } In both cases, I think the Ruby solution is more readable. Python list comprehensions invert the subject (data) and the verb (action). You see what will be done before you see what the subject is. I would argue that showing the subject first allows easier code review as you know immediately what you are working with. But beyond that, the first Ruby example tells you in English what is happening. "take this range", "select a subset", then "map some actions to the elements". And the filter_map abbreviation does the same, telling you "take this range, filter it and perform an operation on the remaining elements". Python tells you nothing... and what it does say is in awkward order. As functional and data-oriented programming is gaining in popularity (for good reason), adopting some functional practices in Ruby is a pleasant experience. Doing the same in Python exposes more of these... irregularities. Edit - I always forget how to format symbols in these comments!
- nuker 4y ago> In both cases, I think the Ruby solution is more readable. Nope.
- robertlagrant 4y agoThe Python one looks fine to me, although I am a Pythonish person. It uses what people already know: the for something in somethings syntax of the for loop, and the if syntax. Also it's nice that this works in dictionaries, generators and lists. It also has the same narrative flow of Haskell's list comprehensions, which I think come from set theory: [x^2 | x <- [0..10], x `mod` 5 /= 0] As for your Ruby examples: I think you could argue that the filter_map version is very readable, but not necessarily more so, but the select one looks pretty painful.