Operator Overloading: Trickier Than it Looks
In an illuminating post (formerly at http://www.jroller.com/scolebourne/ but the domain was taken over by spammers) Stephen Colebourne demonstrates exactly why operator overloading is a bad idea. Now mind, you, he doesn’t think that’s what he’s doing. He actually believes he’s making the case for operator overloading. However the mistakes he makes are exactly the sort of mistakes likely to be made by almost anyone without at least an undergraduate degree in mathematics who tries to overload +
, -
, *
, and especially /
. If you don’t know the difference between a group, a ring, and a field, you have no business overloading operators.
Now I’m not saying that one has to take a course in abstract algebra before you can be a competent programmer. You don’t as long as the language you program in doesn’t support operator overloading (or as long as you’re wise enough not to use it if it does). However since most programmers are didn’t get beyond ODEs in college (if indeed they got that far–some of my comp sci friends struggled mightily with calculus and had to retake it repeatedly), one can’t responsibly design a language that requires mathematical sophistication in the 99th percentile for proper use. Probably one should stop with high school algebra.
Features that are guaranteed to be misused should be eliminated. In the specific case of operator overloading, I daresay, the feature will be misused far more often than it will be used properly. There are only about a dozen or so cases where operator overloading is called for, and only one is even remotely common. That one is complex arithmetic, and this single use case could be better handled by adding a complex type to the language. Arguably BigDecimal
and BigInteger
are a second and a third. The remaining cases are so incredibly esoteric that only a mathematician or an occasional physicist could possibly be interested in them. Can anyone name even one?
October 8th, 2007 at 7:43 pm
I agree, Elliotte. I’ve written tens of thousands of lines of C++ code, and have almost never found any use for operator overloading. Why make C++ code even harder to understand than it already is; or more importantly, why make Java as hard to understand as C++?
October 8th, 2007 at 9:09 pm
Are you saying that one can’t add apples and oranges? One certainly can, so long as he ends up with a quantity of fruit.
If one doesn’t know the difference between a group and a ring, then perhaps he should choose to overload either addition or multiplication, but not both. I don’t imagine that anyone who is contemplating language design would seriously consider multiplying two apple variables together, but it certainly would be valid to multiply an apple and an int. It doesn’t take a scholar to appreciate the difference between an apple and an int, and Colebourne isn’t focusing on overloading anyway. He’s discussing how early language decisions have made Java less flexible in supporting new types.
You don’t mind adding a complex type, but you don’t want to add duration types? I’d suggest looking at the temporal stuff of Date, Darwen, and Lorentzos. There are many more complications than just those mentioned by Colebourne. The more those complications can be organized by the standard library, the more useful the language becomes. Colebourne’s point isn’t so much that the esoteric language desiderata he identifies would be useful for the average programmer, but that library authors (at least some of whom have presumably studied enough abstract algebra) could use them to increase the utility of the libraries used by the average programmer.
As for your last question: What about matrices? The rules of basic linear algebra are straightforward enough, and matrices are useful for many things. Quaternions certainly can sneak in the same door as complex numbers, and I’m told that they are useful for graphics work (admittedly I haven’t done much of that). For that matter, there are several useful binary operations in graphics work. I haven’t thought about them much, but some of them might even commute. For certain bookkeeping situations it’s useful to be very careful with the distributive law, and I can see operator overloading being helpful for that as well.
Don’t sell your friends short on abstract algebra, anyway. I found it much easier than my second semester of multivariable calculus.
October 8th, 2007 at 11:18 pm
Check out Boost.Spirit for some great use of operator overloading in C++, I’ve been working with that recently. Dave Megginson, if you’ve never used the std containers and iterators in C++ then you’re just programming in C with classes, and all that stuff depends on operator overloading.
Not a ton of overloading, but a judicious, well-designed amount. Which is of course what people who don’t want hard things in Java don’t want you to know about.
October 9th, 2007 at 1:05 am
The code I wrote to implement solvers for sets of linear equations would have been more pleasant to write and read if I had not been forced to write:
newVar(‘x’).times(2).plus(newVar(‘y’).times(3)).lessThan(10);
Wouldn’t you rather see:
x = newVar(‘x’);
y = newVar(‘y’);
2 * x + 3 * y
? I would have. And if I’d been writing C++, the third-party optimization library (Xpress-MP) would’ve allowed me to write the latter. Alas, I was using their Java API. (I’m just giving a use case, not arguing for operator overloading in Java. :-))
October 9th, 2007 at 1:06 am
Grr. I meant:
2 * x + 3 * y < 10
October 9th, 2007 at 3:35 am
What about operations on sets? Or matrices and vectors? Or rational numbers? Plenty of programmers deal with these all the time.
October 9th, 2007 at 5:56 am
You can add apples and apples, or oranges and oranges, but then he suggests multiplying them. That doesn’t work. You end up with apples squared, not more apples. What he really wants is to multiply 7 apples by 3 (no units). Then to make it commutative you have to overload addition on ints as well as apples. All of this is possible. However it’s extremely tricky to get right and unless you have a lot of experience with this sort of math, and a lot of time, you’re far more likely to get it wrong than right.
Sets have standard operations–union, intersection, difference–but these are not the same as addition, subtraction, multiplication, and division. I know some set theory books use the same symbols, but I always find those books to be very confusing and hard to read. I much prefer to read books that use different symbols such as ∩ and ∪, and I’d like the same thing for my code. If I can’t have that, I’d rather spell out union and intersection.
Matrices and vectors are a classic case that sounds like a good example, but usually isn’t. To add and multiply vectors and matrices the dimensions have to be consistent. If they’re not, then you’d have to throw an exception. No one expects the plus sign to throw an exception. And division is even trickier. Quaternions are actually one of the esoteric cases I was referring to; but if anyone here can explain what a quaternion is without looking it up in Wikipedia first, I’ll be very impressed. I certainly couldn’t.
Rational numbers are at least a mathematically well-defined field so +, -, *, and / all make sense; and unlike quaternions typical programmers do understand rational numbers. But how often do developers actually need rational as opposed to floating point numbers? I’m not sure I’ve ever encountered a use case for that, though doubtless there are some out there somewhere.
The linear solver library is an interesting case. I’d really need to explore it further. However I would have suggested a different tack on the API there. Something like this:
Equation eqn = Equation.parse("z = 2*x + 3*y")
Let the library do the work of parsing the equations rather than making the programmer define it in individual method calls.
October 9th, 2007 at 10:07 am
The problem is a lot bigger than Apples or Oranges. It’s called The Units Problem.
You might add Apples to Apples, but you’d never multiply them. If there’s any multiplication at all, it’s by a pure number, not a numbered unit. And in a shop inventory situation, I don’t see where multiplication would be a generalized operation anyway. If anything, Apple is a subclass of StockItem, and it allows multiplying by a pure number, provided by a LineItem or something.
The problem gets harder with other units. Inches times a pure number gives inches. Inches times inches gives square-inches (area). In general, * gives , but we all remember that from grade school.
And on a completely different tack, if one is so interested in preserving units, then one writes source in a language that supports units, even if one has to write that language first. I’ve written more than one pre-processor, and various little languages to do specific things. I really don’t think it’s that big a deal unless one gets carried away trying to over-generalize (not an uncommon problem).
October 9th, 2007 at 10:23 am
Rereading the article, something else occurred to me: Apples and Oranges aren’t real shop items. Real shops have items like {Apple, Braeburn, New Zealand, Conventionally grown, …} and {Apple, Braeburn, New Zealand, Organically grown, …}. And for perishables, I wouldn’t be surprised if there were sell-by dates and supplier codes and other things involved, too.
In short, there’s a lot more to stock-keeping units than meets the eye. Operator overloading is almost the least of the difficulties. The answer is the same, though: if you want a specialized stock-keeping language, write that first, then express your stock-keeping program in that.
October 9th, 2007 at 10:50 am
I worked on rewrite of a C++ Virtual memory system for an un-named Unix system and used concrete types to separate physical vs virtual pages, etc. and used operator overloading cleanly. Bad things can happen if you start adding virtual pages to physical page addresses. But I guess this is just a special case of “complex type”.
October 10th, 2007 at 3:39 am
It’s a variety of requirements myopy: some operator can be easily and usefully defined for some values, so why not define it for their types? And for convenience and completeness, let’s add other relevant types (e.g. Apple+int) and other expected operators!
I don’t think mastery of algebra is really required to get operators right: it might be obvious for any expert that operators on Apples and similar physical quantities MUST NOT make a field of them and should stop at addition and subtraction, but anyone that stops to think can realize that dividing apples might make sense but doesn’t produce apples and that multiplying apples makes very little sense; finding problems like division by zero requires only marginally careful analysis (or casual unit tests) of software correctness.
October 10th, 2007 at 10:48 am
Regarding the linear solver library: often the coeffecients themselves were in fact variable, so constructing the string properly, as shown in the sample above, would’ve been a chore. It was more the classic case of a vector of scalars multiplied by a vector (of equal length) of real-, integer- or binary-valued variables (see for example the first set of equations here: http://en.wikipedia.org/wiki/Linear_programming). Also, in some cases, you would want to get the values of the variables back, and in others to apply additional constraints — i.e., fix the value of one variable — and re-run the solver. So while I like the looks of
Equation eqn = Equation.parse("z = 2*x + 3*y")
I don’t know whether it would have been a win for us. (In fact, now that I think of it, one colleague did take this path, more or less: He wrote a program that wrote programs in the vendor’s proprietary scripting language, which *did* allow math-like expressions, and then invoked their scripting engine to evaluate them. That was about as much fun as it sounds.)
October 10th, 2007 at 1:33 pm
“one can’t responsibly design a language that requires mathematical sophistication in the 99th percentile for proper use.”
Good object oriented design is hard too. Harder than understanding a small set of axioms for group/field/etc. Are you going to advocate the removal of the “class” keyword too? 🙂
Quaternions are not that obscure, actually. They are frequently used in 3D graphics to express rotations. They can be explained without reference to the Wikipedia article quite simply (i^2 = j^2 = k^2 = -1, ijk=-1).
Operator overloading is nice when you can write generic algorithms which can operate on any object implementing the operators you are using. Otherwise, it is just syntax sugar and it is not clear if it provides any real benefit.
For example, the “greatest common divisor” algorithm can be used with not only integers, but polynomials, integers mod n, and other objects. Many other functions make sense on numbers as well as vectors and matrices.
In Java there’s no way to express this polymorphism in the language. I remember a few years ago having to implement the same integer-valued function twice, once on ‘long’ values and another time on ‘BigInteger’s.
Also in the java.lang.Math class, basic stuff like min, max has to be re-implemented for each primitive number type.
Java generics don’t go as far as making operators generic. C++ doesn’t have this problem, because with templates math operators enjoy compile-time polymorphism.
If your math operators are polymorphic, then allowing user-defined types to respond to them increases the expressiveness of the language.
Otherwise it is needless complexity.
October 11th, 2007 at 4:19 am
Sorry to be rude, but who gives a sh** about apples, orange or quaternions (whatever it is) ?
Arithmetic operator overloading makes total sens when talking about a date library (and I know some other financial stuff where it does as well).
I do not especially advocate for operator overloading (despite I really missed it when switching from C++ to Java) but it is easy to badly design a library whether you have operator overloading or not.
October 11th, 2007 at 6:03 am
I think operator overloading is difficult to do and current approaches from other languages are lacking, that is why so many people are against it. We need a fresh approach because operators are slightly different than normal methods in that they are related to each other and in general efficiency is a concern. Let me address efficiency first, you don’t want:
r = a + b + c + d
to be:
r = a.add(b).add(c).add(d)
because each plus needs to make a new object (think Strings here). You want:
r = a + b + c + d
to be something like:
final SomeType temp = a.toOperableForm();
temp.setAdd(b);
temp.setAdd(c);
temp.setAdd(d);
r = temp.fromOperableForm();
This is how String behaves, with good reason! Therefore I suggest defining:
public interface AddOperator > {
T toOperableForm(); /* makes a deep copy and can cast if required */
}
public interface Addable > {
T fromOperableForm(); /* makes a deep copy */
void setAdd( T t ); /* changes the value by adding */
T unity(); /* returns unity (1) */
}
Then a class that implements AddOperator can be used with operators +, +=, ++ (pre), and ++ (post) by transforming the code as shown above for +. String would therefore implement AddOperator and StringBuilder Addable. Some classes may implement both AddOperator and Addable.
Note how the programmer implements setAdd and unity; but all the operators +, +=, ++ (pre), and ++ (post) are defined in terms of just setAdd and unity, thus giving consistency between operations. This is the second problem with operator overloading addressed, i.e. when temp = a; temp + b != a += b
Similarly the interfaces:
interface ArithmeticOperators > extends AddOperator {
/* empty */
}
interface Arithmeticable > extends Addable {
void setDivide( T t );
void setMultiple( T t );
void setSubtract( T t );
}
And a class that implemented ArithmeticOperators could be used with operators like AddOperator above but with /, *, and – as well as +, i.e. /=, *=, -=, etc.
Comparing is simpler than arithmetic because an intermediate class isn’t needed because compare doesn’t change any values:
public interface CompareOperators > extends Comparable { /* compatible with old code because it extends Comparable */
/* empty */
}
Would allow , !=, and == for a class that implemented CompareOperators. Currently, if you are implementing Comparable you shouldn’t use == and equals should call compareTo. This solution elevates the problem of calling ==, but the problem of people forgetting equals remains (but is lessened since most people will use ==).
October 13th, 2007 at 10:19 am
Elliotte, I’m usually with you, but in that case I strongly disagree. Of course, one can misuse operator overloading and obtain dangerous, maybe even lethal programs. But, hey, would you prevent architects to use stairs in new homes because, indeed, some people are dying in stair cases? Let people chose for themselves, I will not force you to use operator overloading, I will not even try to convince you to do so, but, by all means, leave me the possibility if I want, to implement vector addition with ‘+’ or to map whatever mathematical operators to whatever mathematical object I have to deal with in my programs.
To broaden the discussion, I guess that judging from this forceful conservatism in language design, and pessimism about the ingenuity of the average human mind, you are against the use of stem cells for research in regenerative medicine and totally disagree with the transhumanist thesis, is it so ? 😉
October 13th, 2007 at 7:08 pm
Mel, you say you won’t force me to use operator overloading; but what happens when I have to maintain your code that does use operator overloading? (or less personally what happens when anyone has to maintain code written by someone else?) What you’re really saying is the old argument that because C/C++/Fortran/Basic can be written cleanly we don’t need languages like Java/Python/Ruby/Ratfor that make clean code a lot easier to write. Years of experience have thoroughly debunked that particular logical fallacy. Experience has proven that if code can be written badly, it will be written badly; and those of us who struggle to write good code will have to clean up the mess the careless and inexperienced devs leave behind them.
For any feature added to a language, one has to weigh the benefits and the costs. Too many people only want to count the benefits and ignore the costs. We know from years of experience with C++ that the benefits of operator overloading are small and that the costs are large. It’s not a reasonable trade-off. Even if you know enough math and are a careful enough programmer to properly use operator overloading without causing problems for yourself or others, rest assured that other less careful developers will still cause problems for you. Operator overloading will cost you a lot more than it pays back.
October 22nd, 2007 at 8:03 am
“Features that are guaranteed to be misused should be eliminated”
Integers overflow without warning, better get rid of them.
Floating point has numerous pitfalls for the unwary, better dump that too.
Classes have already been mentioned.
If rationals were widely available and easy to use, they might be used more commonly. They do correspond more accurately to the arithmetic that you learn at school.
I think limited overloading should be allowed, but only for the +,-,*,/ operators and only for classes extending a restricted number of interfaces (Group, Ring, Field). The interface documentation would naturally include a description of the properties expected of implementations.
October 27th, 2007 at 12:02 pm
Better get rid of arrays too then, since it allows an anonymous class to capture a non-final variable:
final Object[] foo = { nonFinalObject };
In fact, lets get rid of the final keyword altogether – 3 semantics behind this one depending on whether its used on a class, on a variable or a method.
Come on now. Stephen’s point is that we’ve perhaps comitted outselves a little too much to strict contractual OO. A little more flexibility in the language goes a long way. Thus, I am certainly in favor of operator overloading, properties & events as well as method pointers (omit the class as part of the signature).
/Casper
October 30th, 2007 at 2:54 pm
Yes overloading is a powerfull but easy to get code buggy feature of a language.
Question is, should that be an argument to exclude it from a language? I used the same argument as ERH once, but now I do no longer…
Because, if you go down that road you go down the road of ‘language can prevent you from writting buggy code’. It can not.
It is you responsibility to a) Know the feature you are using. b) Test your code. c) Take advantage of code written by developers more knowledgable of features you are not (say a matrix library in c++).
Will programmers screw up, yep. Leaning on a language to prevent that from happening is more dangerous than operator overloading or generics or what have you. You must take responsibility for your project as honestly you can.
This reality should not deny us tools.
October 31st, 2007 at 7:27 pm
One cannot but disagree with ERH. Mathematicians have been perfectly fine with using +, -, … for centuries but since ERH fears that I cannot program properly I must work with a clumsy notation like a.plus(b).times(c) rather than (a+b)*c (whatever a, b, c are).
ERH is asking for examples, plenty were given already. Whenever you can define a consistent well-defined algebra you may want to express it concisely using operators. And there are plenty of cases. An example is the loan pie chart from Eric Evans’s DDD book. Or dates and periods or matrices, or rings, or groups, or …
The problem is not using symbols like + instead of plus. That is a trivial language extension as shown by B Meyer for Eiffel. I need to write a method plus in the first place and have to get that right, but that I need to do anyway. So ERH’s argument in the end is only about style: a.plus(b) vs a+b. But tastes differ, and I wouldn’t want ERH to be the final arbiter on that. I prefer a+b where it makes sense for me. So let’s have operators asap.
November 5th, 2007 at 10:15 am
“Features that are guaranteed to be misused should be eliminatedâ€
I could not agree more. The thing with operator overloading is that it sounds very cool, and you can produce a lot of readable code fast, but by the time you realize this code is only readable for you (because your colleague’s intuition about what SpaceShuttle.+( GreenPeaSoup p ) should do is different than yours), the project is too far ahead, and a re-write is too costly. And there will always be someone who will try to use it again and would break things up again.
Look at ruby for example. Can you tell the difference betweem ==, ===, equal?(obj) and eql?(obj) ?
Long, meaningful names are a blessing. You write once, you maintain for the application lifetime. Keep that in mind when you want your language to be able to do very complex stuff in a single expression.
November 7th, 2007 at 2:04 pm
It is most annoying that so many commentators here assume that those of us who want operator overloading are being assigned the worst-case examples like Michael Bar-Sinai’s SpaceShuttle.+(GreenPeaSoup p). This is clear exaggeration. But even in more modest examples, like DateTime + DateTime, it is presumed that because operators are available the proponents of operators will use them while the equivalent DateTime.plus(DateTime) will never be written, of course, since the writers of such code are automatically soooo much sounder of mind! Nonsense.
In each case, you must come up with a sensible set of methods (once complete, consistent and well-defined you can even call it an algebra), give them proper names (plus or +, minus or -, … whatever), implement and document them. In the end it remains a matter of style not substance, and I prefer the more concise mathematical operator. Let me have it.
In the end let the marketplace of ideas, of APIs decide which ones are being used by programmers, not some self-appointed style police.
November 16th, 2007 at 1:02 pm
Depriving users of a whole class of meaningful domain-specific function names because they are not alphanumeric seems a bit dogmatic. Software outgrew globally unique identifiers some time back. On the other hand, adding operator overloading at this time to Java could only create confusion.
Look at Scala (scala-lang.org) for an elegant approach to operator overloading, coupled with serious type checking.
November 22nd, 2007 at 1:52 pm
(% (/ banana knife) (+ milk cereal))
November 23rd, 2007 at 5:00 pm
As Werner said above, as long as you can define a consistent algebra then overloading is fine. Any class which implicitly represents a number is a natural candidate — BigInteger, BigDecimal, Rational, Complex, etc.
Other cases where I’ve used operator overloading (in C++) involved “normal” types that were wrapped in a class because they needed to be proxied somehow: atomic integers and pointers, byte-swapped integers, and objects in another address space which have to be accessed by DMA. (The last for the PS3 SPU.) All fine candidates for overloading.
February 7th, 2008 at 10:40 pm
The only good operator overloading that I’ve seen in “+” for string concatenation. ADA was the first language that I saw that had it, and I thought it was cool, because you could define Operator Overloading for Money and have it work the way people thing it does.
But as I’ve gotten older, I’ve grown to dislike it intensely.
Back when we engineers were taught to use slide rules, we were taught to work out the units in our head. Feet x pounds should give you torque, in either foot-pounds or pound-feet.
The idea of ‘apple^2’ is really dumb. Its not often part of folks brainwaves, but you never multiply money, you multiply one money quantity by a dimensionless number.
I totally agree. then again, I have a BS in Mathematics, and like fields, groups, isomophisms, etc.
March 3rd, 2008 at 8:42 pm
By way of disclosure, I am a math/physics major who did all that abstract nonsense in school, and I currently do mostly financial, physics, and graphics programming, so I’m pretty biased here.
In any case, here’s the problem – I can get behind the claim that there are very few reasonable uses of operator overloading other than the canonical mathematical ones (vectors, matrices, big numbers, quaternions, etc.). The apples/oranges example is not great. That’s fine. But since Java doesn’t include a decent assortment of mathematical objects that follow usual mathematical notations within the language, it is ridiculous that it also doesn’t include the ability to add this functionality. This is why people keep complaining – because they are tired of the parenthesis-soup that results when you try to write out simple vector equations.
If Java contained built in Vec2, Vec3, Matrix, and Quaternion types that used the usual operators (yes, I understand that adding two matrices of different size would cause an exception), I would be happy (happier if they behaved as value-types and could be reasonably quickly created for temp purposes, unmet-and-delayed promises of escape analysis notwithstanding), and I would gladly accept the fact that it allows no operator overloading. Lacking that, Java is obnoxiously deficient for mathematical purposes (which, to be fair, Sun has told us many times they don’t care very much about), and the only reasonable solution I can see is to allow overloading.
If you want to protect everyone from the bad programmers, then you have to at least give the good ones the tools they need to be productive and write readable code. Otherwise Java will continue down the path it’s on, until its sad death when all the halfway decent programmers have decided that they have no interest in programming a weak language that’s designed for bad programmers. I truly hope this doesn’t happen, because I happen to love the language otherwise.
March 22nd, 2008 at 6:17 am
The true usefulness of operator overloading comes with generic programming, which is sadly extremely limited in Java. Just try to do stuff like position += delta in a generic way without operator overloads..
Combine this with something like C++09 concepts, which do a compile-time check on the operands, and you get compile-time safety for these overloads (well, its possible even with C++98, but its non-trivial).
This outlines an important fact: operator overloads are *very* useful, BUT need very strict checks, since they can be abused very easily. C++ is getting there with concepts, Haskell got it right. Java decided not to deal with that problem simply by throwing out operator overloads altogether, thus diminishing its expressiveness. (Its real sin are the heavily castrated Generics, though – if Java Generics were a match to C++ templates, it would be a far more powerful language, especially because they had the chance to design a different syntax, much better suited for the tasks that C++ templates are used in today).
A great problem seems to be that many people still see operator overloads and the like from a 90s point of view. Just have a look at stuff like Boost, Generic Haskell, the upcoming C++09 standard, and then talk about operator overloads again.
April 5th, 2008 at 10:20 am
If operator overloading is really that bad, then I must of been condemned to C++ Hell. My assignment in college is to develop a program that overloads all math operators, the cin, the cout, and the equals sign.
What if it’s the case where we have no choice but to overload a bunch of math operators?
April 14th, 2008 at 3:03 pm
Predefined overloaded operators for the Collections class operations, BigInteger, etc would be very nice.
User defined operators don’t seem objectionable in actual practice. I’ve rarely seen them abused in real C++ programs. There are plenty of features that can be misused, but most are not because of education, social pressure, or most often just good sense. The programmer who overloads + for Apples would be likely to write a plus() method too.
Units and range restrictions are something I really, really miss in Java. These are so awkward to implement that people use unitless ints for their apples, which does allow the dreaded apples**2 . Allowing operator overloaded and/or implementing units would surely reduce the number of squared apples.
July 28th, 2008 at 6:48 pm
how can i overload an operator in a generic class in c#?