When I was reading "Programming in Scala" to learn the language, I came across some elements that reminded me of things that were in C++, but were left out of Java because they were seen to be dangerous and caused problems. One of these is operator overloading. This blog post is largely in response to a post called "'Operator Overloading' in Scala". That post gives a good description of the basics so I won't repeat it here. I mainly want to write about something I feel is missed, that is the reason why what Scala does is different from C++, and why Scala's version is less dangerous.
The problem people generally associate with operator overloading is that you can produce unreadable and confusing code. Something like overloading + to do multiplication. I think this is a poor description of the problem. You can write unreadable code without operator overloading as well. For example, you could write a Java method called add which does multiplication. No reasonable developer would do that, and allowing operator overloading doesn't suddenly make people stupid. The real problem with C++ operator overloading is that it is limited to normal operator names. To see why this matters, imagine a situation in Java where you were restricted to method names like "add", "subtract", "multiply", and "divide". Now you implement a vector class that needs methods like dot product and cross product. What do you do?
In C++ if you have any operations that are similar to mathematical operations, the language basically seduces you into using normal operators for them. It is easier to type * than to have a full method call. Perhaps * isn't a perfect fit, but it is the best you can use and you think it will make code easier to work with. The problem is that when other people use the code, the * looks just like any other multiplication and it isn't immediately obvious it is doing something different.
This is where Scala is a major improvement. Operators aren't limited to standard names. They can use any combination of operator symbol characters. So if you had the vector class mentioned above you could use *+* for dot product and *** for cross product. I won't argue those are great names. Indeed, I would probably argue against them. However, when another programmer sees a *+* b in code, he/she will know this is not normal multiplication, and know something needs to be looked up.
Scala collections, in my opinion, make good use of this. For example, consider a sequence called stuff and a single value called v. You can prepend and append to stuff with a +: stuff and stuff :+ a respectively. If moreStuff is another sequence, then stuff ++ moreStuff appends the two sequences. This type of usage is highly readable, but wouldn't be possible in the C++ model.
One of the great things about the Scala syntax is that these symbolic operators are not special cases. Any method that takes zero or one arguments can be used in operator notation. So instead of using *+* and ***, you could simply use dot and cross. In the code you could then write a dot b or a cross b. The only drawback to this approach is that the dot and cross methods will have lower precedence than +. So the expression c + a cross b will be seen as (c + a) cross b instead of c + (a cross b). If you use ***, the normal precedence rules will give you the latter form, which is what is normally expected in vector math.
In summary, I think that the creators of Java were wise to not follow in the footsteps of C++ with operator overloading. It really does push programmers into doing some silly things. However, Java was probably an overreaction. With the approach taken in Scala, we get a best of both worlds. You can have operators when you really have mathematical operations and you get the simplified, infix operator syntax. However, you aren't restricted to basic operator names, or even symbolic operators in order to use infix notation.
Operator overloading in C++
ReplyDelete