But I guess in terms of strings let's take a moment and remember the one truly good design decision that Python devs made about strings (which isn't usual for an imperative language). Strings are immutable by default.
>>> import numpy as np
>>> xs = np.arange(9).reshape((3,3))
>>> xs
array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
>>> sum(xs) # builtin sum, not numpy sum
array([ 9, 12, 15])
>>> sum([[3, 11], [5]], [])
[3, 11, 5]
>>> sum([(3, 11), (5,)], ())
(3, 11, 5)
However, that doesn't work for strings; there's a special-case check for that.Erm, Haskell people write code that can do this all the time - you write stuff in terms of generic monoid and then it just does. I can even think of a concrete case where I used those two examples: I had essentially a report that looped over a bunch of rows and accumulated something as a secondary effect, and in one client's case that was a string explaining what had happened, and in another client's case that was a count of how many things had been processed.
Batman!
It's not possible to write the generic code since there are basically no constraints. Generic code is all about constructs with certain invariants and use those invariants to create something new. Javascript addition basically has no invariants.
I would argue that that's not true.
JS is a "mono-typed" language and there is therefore only one type of things you can operate on and return as a result.
>It's not possible to write the generic code since there are basically no constraints. Generic code is all about constructs with certain invariants and use those invariants to create something new. Javascript addition basically has no invariants.
That's backwards to my point. I tried to point out that having generic code which can operate on (almost) arbitrary data structures mixing for example natural numbers or lists of arbitrary things will lead in the end to the same puzzling results as the "classical JS-Batman" example.
Don't get me wrong, generics are a great tool! But imho it's easy to "overstretch" their usage until a point where things become "so generic" that your functions can operate on "almost everything" but the results become as surprising as the alleged "JS insanities".
Something like the Monoid abstraction is incredibly powerful (most languages can't express such an abstraction in a sensible way even). Using such a powerful tool to abstract over some very concrete and banal things (that would be handled better separately as they're "not the same", no matter there may be a deep mathematical connection) is not a good architecture imo. If your code handling "concrete things" is written in a very generic and abstract way you won't have any "normal" tools left on your belt in case you would need to abstract over that stuff you've just written (at this point you could enter type-level programming or something that will achieve something similar but you will than end up with laughable complex code to handle in the end banal business logic).
Javascript has a type system that is enforced at run-time.
> That's backwards to my point. I tried to point out that having generic code which can operate on (almost) arbitrary data structures mixing for example natural numbers or lists of arbitrary things will lead in the end to the same puzzling results as the "classical JS-Batman" example.
I don't agree. The entire point of the link you posted was the insanity of the semantic of the addition operations in Javascript. Monoids are very simple and well defined. The javascript insanity would need pages upon pages of explanations because basically everything is a special case. In contrast monoids can be fully explained in a two sentences.
>Javascript has a type system that is enforced at run-time.
Nobody questions that JS has "runtime type-checks". But that's irrelevant to my point. I've referenced of course this here:
https://existentialtype.wordpress.com/2011/03/19/dynamic-lan...
>Monoids are very simple and well defined.
Sure. Just define an instance for a type that models a JS runtime-object, let's see what happens… ;-)
But I see, my point is still not explained well: What I'm trying to say is that using abstractions that are so powerful that the resulting code can operate on (almost) arbitrary "unrelated" things using that code will lead to the same kind of puzzling outcomes as "JS Watman".
(And like I said, a deep mathematical relation doesn't make things "related" in a practical sense automatically. For example when it comes to "correct" domain modeling of business cases where you explicitly want to treat different parts of your system separately; as this results in a less "entangled" architecture. Gluing together parts that are unrelated through clever code on the other hands side creates maintenance hell where everything depends on everything, but you can't even see what depends on what as everything is generic; that's the opposite of good software engineering, imho. To throw a few phrases: "Divide and conquer!" "Use the right tool for the job!" "Use the least powerful tool that gets the job done well!").
Well, in that case programmers should not be surprised when they subtract an integer from a string and get NaN. Apparently they are are surprised though. I'd prefer my language not let me subtract an integer from a string, since if I ever attempt to subtract an integer from a string that's almost certainly an error.
> That's backwards to my point. I tried to point out that having generic code which can operate on (almost) arbitrary data structures mixing for example natural numbers or lists of arbitrary things will lead in the end to the same puzzling results as the "classical JS-Batman" example.
But that's not true. Nothing you do with a monoid will create such a puzzling result, because summing a list via a monoid is - by definition - equivalent to summing it by calling + pairwise. (Of course, if you have a type for which + behaves in a confusing way, then this might be confusing - but it's not the monoid that's being confusing there).
If you want to claim monoid-based code can be confusing, try using some actual examples of monoid-based code, rather than code in a different language that isn't using monoids and wouldn't compile if it did.
> If your code handling "concrete things" is written in a very generic and abstract way you won't have any "normal" tools left on your belt in case you would need to abstract over that stuff you've just written (at this point you could enter type-level programming or something that will achieve something similar but you will than end up with laughable complex code to handle in the end banal business logic).
This is completely wrong. If you write your code generically it becomes easier to abstract over, not harder. For example, if you write a sort function that operates generically from lists and separate that from your comparison function instead of writing separate functions for sorting lists of integers and lists of strings, it's much easier to then make further abstractions over your sorting.
> https://en.wikipedia.org/wiki/Rule_of_least_power
Again you're getting it backwards. Code written in terms of monoids is less powerful than code written in terms of a specific datatype, which means there are far fewer ways to get it wrong.
The problem with Python is that it doesn't have typing that can express "I can sum".
# 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 ProtocolC++ 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!
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.
sum(map(mappingFunction, iterable))
works fine as long as mappingFunction maps the elements of an iterable to numbers.This works
from functools import reduce
nums = [1,2,3]
def add(p, q): return p + q
print(reduce(add, nums))
strs = ["how", "are", "you"]
print(reduce(add, strs))
so why not that?Concatenation is not addition in my opinion
I've often wanted something like bits++bits concatenation to work in the same way for numbers as for strings
For python, syntactically it is. In a general sense, an operation is what you define it to be. Just my opinion.
x+=y is equivalent to x.append(y)
x = x + y creates a new list, then assign it to x (highly inefficient in a loop)
This is true for lists in Python. `a = a + b` creates a new list, but `a += b` works in-place:
>>> a = [1, 2]
>>> b = a
>>> a = a + [3]
>>> print(a, b)
[1, 2, 3] [1, 2]
>>> a = [1, 2]
>>> b = a
>>> a += [3]
>>> print(a, b)
[1, 2, 3] [1, 2, 3]
There are other issues with the `+=`-style operators as well - IMO it was a mistake to have `a += b` be anything other than a shorthand for `a = a + b`.Perhaps not stupid, but ignorant, and not just of the special casing in sum which exist because of a footgun.
sum is basically a readability alternative for functools.reduce for adding numbers, and it's special cased for strings because though it would superficially work for strings otherwise, the performance of repeated string concatenations is pathological in python (as in many garbage-collected languages where it forces many allocations), so a special cases was added which points people to the method to produce exactly the same result without the pathological performance issue.
If you are doing something more complex than numbers with heterogenous data that includes strings in a reduction by the addition operator so that the join you'd use with homogeneous collections of strings isn't what you want (I'm not sure that hypothetical is ever going to happen), use functools.reduce, which for readability you probably should be using in preference to sum() if you aren't doing a mathematical sum anyway.
No, you don't, because: (1) if your idea of a sum is just reducing with “+”, you use functools.reduce; and (2) if your idea of sum is a mathematical sum, you use the very slight shortcut of the sum() built-in function, which is (barring things like the runtime type checking) equivalent in result to:
def sum(iterable, start=0):
return functools.reduce(lambda x,y: x+y, iterable, start)