I disagree about the operator overloading. It's kind of like a hidden function that could have ambiguous connotations. It could confuse people because of the association with the traditional operations.
For simple things like list addition it might be intuitive what the result will be, but if I had a a Vector class and then you were reading some code and saw two vectors being multiplied like this
a * b
well is that a dot product or a cross product? Or maybe a is a scalar and it's scalar multiplication? If it's one of those what do the other operations get for symbols? isn't it easier to do a.cross(b)?
If you keep it simple a little syntactic sugar is probably find. Nobody wants to be writing a.add(b.subtract(c.mult(d))) for a + b - c *d .
A lot of this depends upon your attitude toward abstraction. For example, there's a Python idiom for flattening a list that is just sum(list, []), taking advantage of the overloaded + for lists. (This is considered bad style for Python; use itertools.chain instead).
However, if you're a Haskeller (or a mathematician), this makes perfect sense. Any type with an associative binary + and an identity element is a monoid; any additive monad (MonadPlus in Haskell) is a monoid; List is a monad; therefore it makes perfect sense that operations like "sum" that are defined on monoids should work on lists.
This is just the age old abstraction vs. traceability debate that's been going on in programming languages for 40 years. Die-hard C programmers look at Java code that says a.dot(b) and say "How can I possibly know what this does? It depends upon the run-time type of a. Much safer just to do vector_add(a, b)." Then Java programmers look at Python code that says a.dot(b) and say "How can I possibly know what this does? I don't even know what interface a and b have to conform to, they could be anything." Then Python programmers look at Haskell code that says a * b and say "How can I possibly know what this does? You say it depends upon the type of a and b, and it could be either a dot or a cross product? And what's this -XOverlappingInstances flag I need to know about?"
Somewhere, there's a happy middle ground between impenetrable code vs. rewriting the same thing over and over again for different types. Personally, I think you won't find it with a blanket rule like "never use operator overloading", and it depends a lot on which abstractions come up in your problem domain.
This is why I like the ability to define your own operator symbols. I think this is a good compromise between the C++/Python approach and the Java approach.
The C++ approach has the problems you outlined. A single operator name (like +) gets overloaded with far too many different--and often completely unrelated meanings. Particularly, this violates reasonable expectations about how certain operators behave; for example, + should be commutative. Python's + for lists, very clearly, isn't.
The Java approach of not allowing operator overloading or custom operators is also patently untenable. Just look at the BigInteger class! There are plenty of other cases when using an infix symbol makes for much clearer code.
With a language like Haskell, you can just come up with new operators as you need them. In fact, they act exactly like normal function names: the only difference is in how they're parsed. This also means they're overloaded like normal functions. So while there is a + operator for a bunch of different types, it always represents some notion of addition. This feature can be abused--it's relatively easy to write confusing operator names--but I've found it to be a net benefit in practice. If used correctly, it makes the code much clearer; you just have to be a little careful.
For example, in Haskell you use ++ to concatenate lists. So you can't mistake list concatenation for addition, which is important because they behave in very different ways.
As an aside, if you're willing to use Unicode symbols--and I think you should be!--then you can define cross as × and write a.cross(b) as a × b. In a small expression this does not seem like much of an advantage, but if you are doing more complicated math it makes the code easier to follow. It's also roughly as easy to type: with the right input mode, it's just a \times b.
> With a language like Haskell, you can just come up with new operators as you need them. In fact, they act exactly like normal function names: the only difference is in how they're parsed. This also means they're overloaded like normal functions. So while there is a + operator for a bunch of different types, it always represents some notion of addition. This feature can be abused--it's relatively easy to write confusing operator names--but I've found it to be a net benefit in practice. If used correctly, it makes the code much clearer; you just have to be a little careful.
I found that in practice it is often abused and leads to code full of .+:, >>> and other operators which certainly make a lot of sense to the author of the library, but don't do much for intuitive understanding of a piece of code without reading the documentation for each imported library.
This can be a problem, but it is entirely a matter of library design. In most cases, I haven't found this to be a problem. It helps to follow certain conventions--for example, a name like <|> can be read as "a different version of |" (which represents alternation).
I think it's still better than what you get with C++ and Python. I would much rather have a relatively inscrutable <+> operator that does something vaguely like addition than having + do multiple completely different things on different types.
Also, for many of these operators, the clarity of having an infix version trumps the fact that you'd have to look it up. Take >>> as an example. Even if it was called something like next, you'd still have to read the Arrow documentation to understand exactly what it did (it's fairly abstract). And compare how the code would look:
actionA >>> some complicated action >>> actionB
next actionA (next (some complicated action) actionB)
Ultimately, it is impossible to design a language that does not allow any bad code. I think having custom operators is better than the main alternatives (C++ and Java styles).
> I think it's still better than what you get with C++ and Python. I would much rather have a relatively inscrutable <+> operator that does something vaguely like addition than having + do multiple completely different things on different types.
I can live with + as a concatenation operator for strings, but in general, if you're starting to implement custom operators for +, you should only do this for things which are numbers, and which can also support -, / and *.
Wrt to Arrow, you could still have your custom operator but with a human-readable name:
actionA >next> some complicated action >next> actionB
or alternatively:
actionA `next` some complicated action `next` actionB
Sure, it's a bit more verbose, but it makes for more readable code (IMO). I think it's not so much the notion of custom operators I object to than the "let's get wild with non-AZERTY characters" philosophy.
Of course, it doesn't mean that human-readable names are always better... I'm looking at you, "return".
If compilers supported proper unicode as code not just in strings (like, I believe, Go does) then you could use A ⊗ B for outer products and A ⋅ B for inner products.
You mean, Unicode symbols. Quite a few languages, including Java, support Unicode letters as identifiers. But frankly, I'd rather stay with cryptic :+: (see the answer to this question [1] for why)
Go has unicode symbols, but I meant just for operators. The reasons listed are important some of the time, but for example when you're a bunch of Chinese developers working on a project it seems sensible to use chinese identifiers in the code (especially if you all only have basic english.)
In this situation, everyone is going to have a computer set up to use the character set, so the ����� + ��� situation shouldn't arise.
In terms of confusing things at linking / runtime stage, I didn't think of that - maybe you're right! Go gets away with it by being completely compiled down to binary (and requiring the source of all the libraries locally, IIRC.)
As a non-native English speaker, I can tell you it's a very bad idea to mix up a language with English keywords but identifiers in a different language. The disconnect between the two is awfully annoying, and the "native language identifiers" end up being a mix of English and native language anyway (I don't know how it is with Chinese but many language don't have useful equivalents to many IT terms, or they are so terrible that nobody wants to use them).
Indeed - I can imagine, with so many english keywords everywhere. The code I've seen from developers who speak other languages tends to be in english, with comments in their language - frequent use of google translate to understand what's going on! :)
I'm wondering about a scala-like language that supports infix operators (i.e. allows one to write "object1 method object2" where Java would require "object1.method(object2)"), but requires all methods to be purely alphabetic, even e.g. "plus". I think writing BigInteger arithmetic as "(a plus b) times c" would still be fairly readable, and current scala goes too far in the other direction (e.g. the main scala HTTP library requires a "periodic table" to tell you which random two-special-character combination does what).
I'm on the fence when it comes to operator overloading. I appreciate the readability gains, but the behavior absolutely has to be predictable.
For example, my biggest gripe with ruby's bcrypt library is how == is non commutative. They do provide a named method alternative, but I was very confused as an infrequent ruby user.
Most types probably won't have two different product methods. If a type did then I think it'd be best to avoid overloading the operator in that case. But for those with just one? Might as well overload!
For simple things like list addition it might be intuitive what the result will be, but if I had a a Vector class and then you were reading some code and saw two vectors being multiplied like this
well is that a dot product or a cross product? Or maybe a is a scalar and it's scalar multiplication? If it's one of those what do the other operations get for symbols? isn't it easier to do a.cross(b)?If you keep it simple a little syntactic sugar is probably find. Nobody wants to be writing a.add(b.subtract(c.mult(d))) for a + b - c *d .
Anyone have other thoughts on this?