A digression on abuse of operators, notation, and "abuse of notation". Steven D'Aprano writes:
On Sun, Aug 30, 2020 at 05:49:50PM +1200, Greg Ewing wrote:
I don't see why we need to pick one use case to bless as the official "true" use case.
We do that for every other operator and operator-like function.
Operator overloading is a thing, so if you want `a + b` to mean looking up a database for `a` and writing it to file `b`, you can. But the blessed use-cases for the `+` operator are numeric addition and sequence concatenation.
Of course the example is abusive. But not being "blessed" isn't the reason. (And shouldn't the notation be "b += a" for that operation? ;-)
Anything else is an abuse of the operator: permitted, possibly a *mild* abuse, but at best it's a bit wiffy.
I don't agree. The reasons for picking a "blessed" use case for operators do not include "to exclude other uses". Rather, they are to help the language designer decide things like precedence and associativity, to give developers some guidance in choosing an operator to overload for a two-argument function, and to provide readers with a familiar analog for the behavior of an operator. For example, sequence concatenation is not "blessed because it's blessed". It's blessed because it just works. What does "5 * 'a' + 'b'" mean to the reader? Is the result 'aaaaab', or 'ababababab'? The reader knows it's the former, and that is the right answer for the developer because 'ababababab' == 5 * 'ab' -- there's another way to construct that without parenthesis. Why is 5 * 'a' == 'aaaaa'? Because 5 * 'a' == 'a' + 'a' + 'a' + 'a' + 'a'. None of this is an accident. There's nothing wiffy about it. Recall that some people don't like the use of '+' for sequence concatenation because the convention in abstract algebra is that '+' denotes an commutative associative binary operation, while '*' need not be commutative. But I think you don't want to use '*' for sequence concatenation in Python because 5 * 'a' * 'b' is not associative: it matters which '*' you execute first. Where things get wiffy is when you have conflicting requirements, such as if for some reason you want '*' to bind more tightly than '+' which in turn should bind at least as tightly as '/'. You can swap the semantics of '+' and '/' but that may not "look right" for the types involved. You may want operator notation badly for concise expression of long programs, but it will always be wiffy in this circumstance. And use of "a + b" to denote "looking up a database for `a` and writing it to file `b`" is abusive, but not of notation. It's an abuse because it doesn't help the reader remember the semantics, and I don't think that operation is anywhere near as composable as arithmetic expressions are.
If you don't like the term "abuse", I should say I am heavily influenced by its use in mathematics where "abuse of notation" is not considered a bad thing:
I think operator overloading and abuse of notation are quite different things, though. Using '*' to denote matrix multiplication or "vertical composition of morphisms in a category" is not an abuse of notation. It's overloading, using the same symbol for a different operation in a different domain. Something like numpy broadcasting *is* an abuse of notation, at least until you get used to it: you have nonconforming matrices, so the operation "should" be undefined (in the usual treatment of matrices). If you take "undefined" *literally*, however, there's nothing to stop you from *defining* the operation for a broader class of environments, and it's not an abuse to do so. So if you have a class Vector and a function mean(x: Vector) -> Vector, v = Vector(1, 2, 3) m = mean(v) # I Feel Like a Number, but I am not variance = (v - m) * (v - m) / len(v) "looks like" an abuse of notation to the high school student targeted by the "let's have a Matrix class in stdlib" thread. But as Numpy arrays show, that's implementable, it's well-defined, and once you get used to it, the meaning is clear, even in the general case. You could do the same computation with lists v = [1, 2, 3] m = sum(v)/len(v) variance = (v - len(v) * [m]) * (v - len(v) * [m]) / len(v) but I don't think that's anywhere near as nice. To call definitions extending the domain of an operator "abuse of notation" is itself an abuse of language, because abuse of notation *can't happen* in programming! Abuse of notation is synecdoche (use of a part to denote the whole) or metonymy (use of a thing to represent something related but different). But do either in programming, and you get an incorrect program. No matter how much you yell at Guido, the Python translator is never going to "get" synecdoche or metonymy -- it's just going to do what you don't want it to do.