9 ms·
Some of these are arguably "incorrect abstractions", but others are just design decisions that are being treated as "incorrect" because they don't follow Haskel
by andolanra 6y ago
Some of these are arguably "incorrect abstractions", but others are just design decisions that are being treated as "incorrect" because they don't follow Haskell's design sensibilities. A perfect example is the complaint about sum in Python:
>>> sum(["a", "b"], start="")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sum() can't sum strings [use ''.join(seq) instead]
I don't think this is incorrect at all! There's a very good reason to use join instead of sum for adding strings together: join is a lot more efficient than simply adding strings together, because it will precompute the amount of space needed for the resulting string at once and then put all the strings into the buffer at once, whereas using sum in the naïve way suggested would do a different append for each element. These are just different tools for different purposes!
Could you design a sum that automatically looked at its argument type and deferred to join for strings? Sure! And that'd be a valid design decision as well. But the specific choice Python made here is motivated and defensible: it's only "incorrect" if your personal definition of "correct" is "built with the same algebraic sensibility that Haskell's designers had," but at that point, all other languages are "incorrect" because they're not Haskell!
- draw_down 6y ago> at that point, all other languages are "incorrect" because they're not Haskell! Right... but, hard not to see that coming as soon as you see the title of the link. In a design context, what could "incorrect" mean but "I don't agree with it"? Ditto "simple".
- mokus 6y agoI agree with your overall point, but regarding your “could you design....” rhetorical: in that example, isn’t it already inspecting the type of the list elements and then doing a specific different thing (printing that message) if it happens to be a list of strings? Why not just go one step further and just do the thing instead of nag the user to do the thing?
- karatestomp 6y agoSum and join mean different things. If sum didn't fail on chars I'd expect it instead to sum their binary values, which is a thing one occasionally actually does want to do, not to join/concatenate them. If it can't do that (or you have strings, as in this case, for which it makes less sense—the most sensible thing there would probably be to treat it as a byte array and do something with that, though any conceivable use of that probably breaks down with UTFs 8 or 16 instead of ascii) I'd want it to fail.
- tom_mellior 6y agoSum is the iteration of +. + on strings is concatenation. If you expected otherwise from sum, it's your expectations that might be wrong. What even is a Python string's "binary value"? Also, Python has no chars.
- uryga 6y agoso you'd expect this behavior? sum("abc") = sum(ord(c) for c in "abc") = sum([97, 98, 99]) = 294 because wow, i... can't think of why you'd ever actually want that. (though it'd be perfectly natural for byte arrays)
- stouset 6y agoByte arrays sure. Not strings. This is why having clear types is important! Strings aren't just byte arrays. Characters aren't just bytes.
- andolanra 6y agoBecause Python's design philosophy is that, for a given operation exposed by the language, there should ideally be a single clear way of doing it, and that philosophy is optimized around reading code rather than writing it. You might need to do a few more edits while you're writing the code, sure, but now a reader of your code will always find sum for numbers and join for strings without even having to examine the context of the code in question. I'm not saying that's necessarily the correct design approach, but it's the one that Python has explicitly established as their guiding principle! A different language will take a different approach: Ruby, for example, likes to build multiple alternative ways of accomplishing the same thing, which leads to multiple aliases for built-in methods or various alternative flags that allow a method to behave in a number of ways. It shouldn't be a surprise that Ruby is fine with the equivalent operation: irb(main):001:0> ["a", "b"].sum(identity="") => "ab" But that's because Ruby's design sensibility is different from Python's. You might like one better than another, but they both come from a consistent vision of how to design programming languages.
- saagarjha 6y ago> Because Python's design philosophy is that, for a given operation exposed by the language, there should ideally be a single clear way of doing it This philosophy falls apart immediately once you start using Python though. I have yet to see it actually be used for anything but not implementing features that someone wants.
- canjobear 6y agoWhat other languages are you comparing against? I came to Python from Perl and the one-way-to-do-it-ness was noticeable and refreshing.
- saagarjha 6y agoI don't think many other languages make that claim, and I think it's generally an anti-goal in diverse multiparadigm languages. I don't even understand why Python tries to have it: I can think of three fairly idiomatic ways to sum a list of numbers right off the top of my head; it's immediately clear that such a proposition is not feasible for the language. The only time I have heard it come up is when there is an unsaid accusation that the language is getting "to complex" and someone wants to block the addition of a feature…
- skybrian 6y agoOne reason is that using "sum" versus "join" helps the reader understand which operation is being performed. You can tell whether the author was expecting a list of strings or a list of numbers. This hint about the author's intent also helps the compiler or runtime generate better error messages. As a reader, you don't know what an abstract operation does, just that it should obey certain rules. Writing code abstractly when abstraction is unnecessary removes information, which has a cost in readability. Now if you want to write abstract code then it will be frustrating, but some languages are optimized for writing concrete code. At least, in this particular case. I'm not sure how consistent Python is about this.
- gugagore 6y agoThis reason doesn't make sense to me, because of `+`. If I do `a + b`, you don't know if my intent is to add numbers, join strings, or concatenate lists. If `sum` and `join` are supposed to look different to convey intent, then the binary operators should look different as well.
- stouset 6y agoThe argument against operator overloading doesn't hold water with me at all. If 3 + 2 is allowed but you don't think "a" + "b" should work, why should 8.1 + 1.9 work? If your argument is that it's because they're numbers, what about a Complex library, or numeric vectors, or GMP? If I can do 3 + 2 and 8.1 + 1.9, I should be allowed to make Complex{0, 1} + Complex{4, 9} work, or Vector{1, 0} + Vector{2, 2}, or BigInt{99999...} + BigInt{9999...}. I can sort of see the argument in dynamically-typed languages when you might not know what the operands are ahead of time, but for statically-typed languages like Rust and Go (Rust gets this very, very right FWIW)? There's no defensible argument against it. Why is `+` magic and holy, but a `fn add(...)` isn't?
- edflsafoiewq 6y agoThat would very greatly increase the complexity of sum. Consider that python is dynamically typed, has heterogeneous lists, and that sum takes an iterable, not just a list. > isn’t it already inspecting the type of the list elements Actually it only checks if the type of start (the initial accumulator value) is a string. If the accumulator becomes a string later on, it's happy to sum strings.
- tom_mellior 6y agoIf that check is there anyway, and the preferred path is known, I would think that the great increase in complexity would look something like this: if trying_to_sum_strings(args): - complain_bitterly() + separator.join(args)
- edflsafoiewq 6y agoLike I said, the current check for trying_to_sum_strings is just that start is a string. So if you would do this you would actually make sum less consistent, since sum([x], ''), which should logically be '' + x, would become ''.join([x]), which not the same (think of an example with __radd__).
- tom_mellior 6y agoI don't follow. In your example, if x is a string, there is no problem since '' + x is just x. If x is not a string, the new path would not trigger, so the behavior would be as before. EDIT: OK, I think if the example is sum([some_string, some_not_string], '') then there is a possibility that some_string + some_not_string might be a valid expression computing a value while ''.join([some_string, some_not_string]) would complain that some_not_string is not a string.
- fractalb 6y ago> Why not just go one step further and just do the thing instead of nag the user to do the thing? At least, readability suffers. "".join(["a", "b"]) is a lot more intuitive than sum(["a", "b"], start="") edit: A beautiful abstraction is the one that feels as good when it's read as when it's written.
- deleted 6y ago[deleted]
- lmm 6y ago> "".join(["a", "b"]) is a lot more intuitive than sum(["a", "b"], start="") No it isn't. The join method being on the separator string is a Pythonism that looks crazy coming from any other language.
- theelous3 6y agoThe point they are making is that join(xs, "") xs.join("") or whatever else you want to come up with is more readable than building more string stuff in to sum, not that having join be a str method is the best way.
- CJefferson 6y agoLits of maths based languages do [1,2] + [3,4] = [4,6], now you really need a different join operator.
- Veedrac 6y agoMagic has the unfortunate downside of preventing people comprehending what's happening. This isn't an issue exclusive to str, and having it silently autocorrect for only that one case will teach people the wrong thing.
- jgwil2 6y agoWhat you just described is a leaky abstraction: you need to understand implementation details in order for it to make sense. Your overall point may be valid but the example seems in that sense to be poorly designed.
- tines 6y agoSpecial cases like this make generic code very hard to write. If I'm writing a library that needs to handle user-provided data agnostic of type and I want to do a sum operation, now I have to add checks all over the place for whatever special cases python has.
- tgv 6y agoThere's no language in which you would be able to write a function for arbitrary data structures using sum(), and get a reasonable outcome. The problem with Python is that it doesn't have typing that can express "I can sum".
- anentropic 6y agoActually, I believe this is expressible in Python's type system: # Python 3.8 from typing import Protocol, TypeVar T = TypeVar('T') class Summable(Protocol[T]): def __add__(self, other: T) -> T: ... In Python 3.4-3.7 you'd need to install typing-extensions library: from typing_extensions import Protocol
- still_grokking 6y agoInteresting. It seems that "every second language" gets "type-classes" these days… C++ added such feature, this above looks like a Python version, Rust has it, Scala had them as one of the first "OOP" languages. It's nice to see that Haskell pioneered a very useful and now spreading language feature!
- sacado2 6y agoAda had them before Haskell, and I'm not even sure it was the first language with them.
- naasking 6y ago> There's no language in which you would be able to write a function for arbitrary data structures using sum(), and get a reasonable outcome. Computing the number of bytes used by a data type seems perfectly reasonable use of sum in that context. This is also achievable in many languages. All 'sum' needs is a map from a value to a number, which can have many sensible meanings even for arbitrary data types.
- antonvs 6y agoIn Haskell, `sum` adds a collection of numbers, i.e. instances of the Num typeclass: Prelude> sum ["a", "b"] <interactive>:4:1: error: • No instance for (Num [Char]) arising from a use of ‘sum’ • In the expression: sum ["a", "b"] In an equation for ‘it’: it = sum ["a", "b"] You appear to be refuting a straw man.
- alexbanks 6y agoA meme: https://imgflip.com/i/4257cd https://imgflip.com/i/4257cd
- maest 6y agoBesides being kinda flippant, I don't see how this aids the discussion in any way.
- theelous3 6y agoThe meme summaries the point very well: "haskell people will say anything that isn't haskell isn't haskell, and is therefore bad". It's ok for a meme to be relevant sometimes. Don't have to do a bunch of intellectual eye rolling just because a Simpsons character is making the point.
- alexbanks 6y agoI agree with the person I responded to - this isn't an honest view of incorrect abstractions in other languages. This is haskellers complaining that other languages are different. The meme I think accurately conveys that, and why not have a meme every now and then?
- enriquto 6y agoThe problem here is that using "sum" or "+" for string concatenation is an idiotic choice of words in the base language (because concatenation is not commutative).
- iainmerrick 6y agoRight! Isn’t concatenation “++” rather than “+” in Haskell for exactly that reason? So if “sum” is a shortcut for “fold with +”, summing a list of strings wouldn’t make sense. Python still isn’t as regular as it could be, of course, as it does use “+” for concatenating strings. I think this can all be justified for ergonomic reasons -- using the simple “+” for strong concatenation is convenient, but “sum” is discouraged for strings because it’s slow. It would be interesting to see a parallel-universe Python that didn’t allow “+” on strings. I wonder if it would have caught on.
- enriquto 6y agoLiterally any symbol other than "+" would work better for string concatenation. For example, the product, the comma, a dot, a space (but this might require other changes in the language), or heck, even the minus sign!
- encypruon 6y agoThere is a rather silly "workaround": In [17]: class Start(object): ...: def __add__(self, other): ...: return other ...: In [18]: sum(["a", "b", "c"], start=Start()) Out[18]: 'abc'
- crimsonalucard1 6y agoIt's incorrect because of consistency issues. Sum works with every overloaded + operator. This is not a Haskell thing nor is it a "personal definition" of correct. People expect a definition to be consistent, this is a generally prevalent notion that transcends the boundaries of "personal" as the general population agrees with this notion. Due to this, under a general definition of "correct" without the context of Haskell such a thing is still classified as generally incorrect. Instead it is more accurate to phrase it as "this is Guido's personal definition of correct." Not that there's anything wrong with that but this is generally unusual and opinionated behavior encoded into python and I wish people will acknowledge that.
- naasking 6y ago> I don't think this is incorrect at all! There's a very good reason to use join instead of sum for adding strings together: join is a lot more efficient than simply adding strings together, because it will precompute the amount of space needed for the resulting string at once and then put all the strings into the buffer at once, whereas using sum in the naïve way suggested would do a different append for each element. Differing performance characteristics should not break the semantics of an abstraction. That's an abstraction leak which prevents writing generalized abstractions.