Monday, March 16, 2009

Operator Overloading Ad Absurdum

Bruce Eckel's recent blog post on The Positive Legacy of C++ and Java has opened a small can of worms on the Internet. The argument is on operator overloading[1]: tool of Satan or road to bliss? The arguing that's resulted is the usual well thought out, reasoned debate I've come to expect from software engineers. Which is to say, not at all.

The Argument Against

Eckel mentions issues with resource management as being the primary reason that operator overloading was seen as a bad thing in C++ and therefore shouldn't be seen as bad in managed environments. But the consensus in the debates is that's not the real issue and Gosling's writing indicates that that wasn't why he dropped it from Java.

The real arguments against operator overloading boil down to abuse of a feature. Even the most ardent anti-operator folks recognize that it is occasionally a good thing to name something +. Their argument against it tends to look something like this.

The problem is abuse. Somebody will name something '+' when it has nothing to do with the common notion of '+'. The resulting confusion is a bigger downside than the benefits of allowing programmers to be flexible in naming.

Phrased that way it sounds like a reasoned trade-off. Yeah, sometimes + is good, but it can lead to more confusion than it helps resolve so let's scrap the whole idea. But watch as I do a little search and replace.

Reductio

The problem is abuse. Somebody will name something 'plus' when it has nothing to do with the common notion of 'plus'.

I've sneakily dropped the last clause but I'll get back to it. Without it, and with the textual replacement, my new statement is obviously true. Naming a function or method "plus" when it doesn't do anything like "plus" is a recipe for throwing people right off the track. I can do it one more time and get another true statement.

The problem is abuse. Somebody will name something 'getName' when it has nothing to do with the common notion of 'getName'.

The statement is still true. The convention in Java is that getName should just return the value of the name property with minimal side effects. But I've seen Java methods named getName used to make permanent changes to a shared database. Certainly you can imagine other ways that getName (or get_name or whatever) is entirely the wrong name for something in any other language - like if it adds numbers.

What's that leave us with? If I can seemingly search and replace with anything and still have a true statement then I must have bit of universal wisdom here.

The problem is abuse. For all valid names X that evoke a common conception, somebody will name something 'X' when it has nothing to do with the common notion of 'X'.

Ad Absurdum

Earlier I removed one phrase from the argument and now I want to stick it back in to the universal wisdom.

The problem is abuse. For all valid names X that evoke a common conception, somebody will name something 'X' when it has nothing to do with the common notion of 'X'. The resulting confusion is a bigger downside than the benefits of allowing programmers to be flexible in naming.

In other words, programmers simply shouldn't be allowed to name things - or at least shouldn't be allowed to use words found in a dictionary.

Where's the Flaw in the Argument

By following the structure of the original argument and with a common observation that misnamed things cause confusion I came to the conclusion that we can't let programmers name things at all or at least can't let them use common words. Good luck trying to figure out a useful process where programmers don't name things or always use gibberish. In the meantime, I'm just going to call the result absurd which leads to the conclusion that the original argument is flawed. To see the problem with the original argument let me make a few observations.

  • Programmers with good taste try to name things reasonably.
  • Most teams have a coding guideline.
  • Smart teams have at least partial code review process to ensure general code quality.

There is a downside to inappropriate naming, but smart teams and individuals already work to mitigate that problem. So the downsides of abuse don't outweigh the benefits of flexible naming because we keep an eye out for abuse in our own code and in others.

This isn't an argument for always trusting programmers with all power. For instance, garbage collection really does take away a huge source of error created by manual memory management. What this is is an argument that operator overloading doesn't change in any fundamental way the core problem of misnaming things nor does it add additional burden to the solutions we already have. If you concede that occasionally + is a useful name (e.g. for complex numbers, matrices, etc.) then the only reasonable conclusion is that programmers should be allowed to use it as a name when it makes sense and strongly discouraged from using when it doesn't - exactly the same guidelines as for any other name.

On the other hand, teams without guidelines, reviews, or good programmers are screwed with or without operator overloading. They've got problems that cannot be fixed at the language level.

footnotes

[1] Throughout this post I've used the common phrase "operator overloading." In a strict pedantic sense that's not really what happens in Scala and Haskell. Those languages simply don't have many operators in the way that most C derived languages do. Instead, they have a very flexible grammar for naming things. In Scala 2 + 3 is the same as sending a + message to a 2 object with a 3 argument 2.+(3) and in Haskell 2 + 3 is the same as calling the + function with 2 and 3 as arguments (+) 2 3. Either approach is actually conceptually much simpler than the C++ approach of having a few special operators plus rules for overloading them. None-the-less, by "operator overloading" I mean Scala and Haskell style flexibility as well as C++'s rigid rules or any other mechanism that lets users define the meaning of symbols like + for their own types.

51 comments:

martin said...

Good points. There's one thing where original C++ overloading is different, though: You only have a dozen or so operators which you can overload. So the temptation to misname things just to get infix notation is much greater.

J. Suereth said...

Good post! Also remember in C++ you could overload important keywords like "new" and "delete". This would cause fun issues if someone subclasses your class without taking that fact into account. Luckily it's less of an issue in java. In general I like being able to overload operators (and not limiting naming). Once again, be prudent! If a developer is not prudent, beat them with raw halibut until they become prudent.

Anonymous said...

But by the same token, I don't see heated arguments about the private modifier, or that the VM checks access to private methods at runtime. But we could do without that as well, couldn't we? What is the difference? If we have a disciplined team, standards, and code reviews, why shouldn't I trust that no one else will try to fiddle with the private bits of my classes? Isn't it more useful, for the sake of debugging, for example, just to make these private fields and methods public, perhaps with an annotation or comment that it should be treated as private? You might argue against the need for private, but it doesn't seem to be a controversial design decision in the same way that operator overloading is. Ditto with macros and preprocessors.

My guess is that op. overloading is more controversial because it's a pain in the neck to have to work without the feature when using the Number subclasses in Java, whereas private modifiers don't cause as much extra effort for programmers, and offer other advantages re: reliability and ease of refactoring.

In other words, I don't buy the "trust the expertise of your programmers" argument as a rule.

Regards
Patrick

James Iry said...

Anonymous,

The argument isn't that we should always trust the programmer to do the right thing. It's that we have to trust the programmer to do the right thing in regards to naming because there is no viable alternative and operator overloading doesn't in any way change that. I'll edit the article to make that point better.

Anonymous said...

I think the difference is that with 'getName()' you easily recognise it as a method, which might do something unexpected. With '+' or '*' this is not the case: you're more likely to assume that they are built-in operators and thus true in meaning.

Of course, complex numbers or matrix manipulation are always used as counter-examples, but I think there are actually very few cases where operator overloading would make a positive difference.

What you gain by using '+'instead of ".plus()" is far outweighed by the increase in complexity when looking at code, as there is now something else that could have been changed.

James Iry said...

@Oliver,

My article does not suggest that it be valid to redefine + for ints or whatever. That's an entirely different issue - redefining existing functions and methods, especially dynamically (aka monkeypatching), has well known problems and benefits but they aren't related to the concept of operator overloading.

And if an experienced user of the language sees x + y and x and y are matrices then of course they will know whether or not the language has a built-in notion of a matrix and corresponding operators or if they need to go look somewhere for the definition.

Finally, your argument completely crumbles when there are (virtually) no operators in the language as in Haskell and Scala. In those languages I'm never confused - it's always a method or function.

Anonymous said...

Python strongly discourages the idea of private class members/variables. This is a way to get something that is almost the same, but even then it is documented how to get at those private methods (the documentation says this scheme may change in future versions).

Conal said...

I'm with you, James. I like operator overloading when it's used well. I recently got much clearer about what uses I like, i.e., what "used well" is for me. I've suggested a general guideline in Denotational design with type class morphisms. It can be summarized as "the meaning of a method (operator) is that method on the meaning".

Even if we remove names as a language feature (and therefore remove the ability to choose names and hence choose them poorly), I think your argument in a more general form applies: "Expressiveness can be used badly. To fix that problem, remove expressiveness as a language feature." Since language is expressiveness, nothing remains.

Landei said...

I never bought the "abuse" argument. It's like trying to forbid sharp knifes because someone could cut himself.

Anonymous said...

The "abuse" argument aside, operator overloading can be a bit tricky. The classic examples in C++ is infix ++ or postfix ++, index operators [] (read vs write).

That said, I hope that it all becomes moot with C++ and we move onto languages such as OCAML, Haskell, Scala, etc.

Anonymous said...

Nice! This is by far the best rebuttal of the operator overloading abuse argument that I've read. I wonder if similar logic could be applied in other arguments, for example about closures :)

Anonymous said...

You forgot the following arguments against operator overloading:

(1) Having a punctuation symbol expand into lots of code goes against the C philosophy, which is (partly) that you don't pay for what you can't see. That's one reason C doesn't have a String class, and why so many complicated operations are bundled into standard library functions instead of built into the syntax. Heck, the original C didn't even let you use "=" to assign one struct to another, because of all the code generation that would involve behind the scenes! C++ diverges from C's philosophy on this count.

(2) Syntax highlighting. Most editors and debuggers (AFAIK) can't deal with overloaded operators, so using them causes maintenance pain for no real gain. C++'s operator() is probably the biggest offender in this area.

Mohamed Samy said...

I like having the ability to overloaded operators when needed, and I generally agree with your post. Just wanted to say the operators do have a somewhat bigger potential for abuse than something like function names.

Unlike names, operators carry more meaning than simply "what operation is to be performed". When using a language with operator overloading, one has to ask a lot of things:

- Should an operator like + always be associative?

- Should something like != be implemented automatically in terms of == or should the programmer handle each operator separately?

- Should an operator like || always be short-circuited?

With careful design, these are solvable problems, but still it's not as simple as it might initially seem :)

James Iry said...

@Mohamed,

Should 'plus' always be associative?

Should 'neq' be implemented in terms of 'eq' or should developers have to write both?

Should 'or' be lazy in its second argument?

In other words what is it about using a short symbol like '+' that makes it fundamentally different from a longer symbol like 'plus'?

Conal said...

> Should 'plus' always be associative? [...]

James - I'm delighted with your post and your replies. I was just about to make the same response when I scrolled down and saw that you'd done it at least as well as I would have.

It's so rare for me to find someone who sees, thinks, and says the same sort of things I see, think, and say. I've gotten a big boost out of finding your blog. Thank you!

Mohamed Samy said...

@James, Conal

In other words what is it about using a short symbol like '+' that makes it fundamentally different from a longer symbol like 'plus'

Special syntax and existing conventional use in the language. For example in C++ we know that when the compiler sees something like + or || it will generate code with certain properties, and that we can rearrange or optimize the code with these properties in mind. Operator overloading makes these operators "downgrade" to simple function call status: no more can I assume anything about these them and I have to fully read the code to understand their behavior.

Some language designers may choose to put special constraints on the overloaded operators to make them behave more like their built-in counterparts while some don't. So it's still a matter of tradeoffs and careful design.

Anonymous said...

+'s don't kill people.
People + people

Daniel Spiewak said...

To prefix what I'm about to say: I'm with you on this one. I personally think that operator overloading alla Scala or Haskell is a very good thing. C++ (and even C#) makes life too painful in this respect and shouldn't be seriously considered for this argument.

With that said, I really understand why a lot of developers are up in arms about operator overloading as a confusing feature. Most developers learned to program in languages which either have no such feature (like Java or C) or which implement it poorly (like C++, C# or even Ruby). I learned to program in these languages first, so I can certainly relate to the bad habits they engender. When such developers look at code, they *habitually* assume that every operator is built into the language at a very primitive level. Thus, when these same developers try to grok code which uses the + operator for non-primitive values, the result is always confusion and misunderstanding of the language feature. Things are even worse when these same developers look at code which uses "non-standard" operators like >>= or infix ++. It's easy to see how this heavy majority of the developer population would develop a passionate hatred of operator overloading.

The solution of course is education. As languages like Scala begin to catch on, people will adjust the way in which they read code (I did). Until then, we're stuck dealing with the everlasting blog posts talking about how confusing operator overloading can be and what an idiotic language feature it is. We need to remember that while these arguments seem quite foolish to those of us who are used to thinking of operators as conventional functions/methods, they are perfectly valid when taken from the perspective of a conventional developer.

Dan Howard said...

Mohamed Samy is correct. The real issue about surface area. How much of a system do you need to understand in order to maintain a part of that system.

Operator overloading can mean the amount of the system you need to understand is the WHOLE system.

You understand that is is what killed SmallTalk right?

Boy we keep repeating the same mistakes....

Daniel Spiewak said...

@Dan

How does operator overloading increase the surface area required for understanding? Operators are really no different from plain-Jane methods in Scala, so the use of operator overloading should be no more or less harmful than the use of named methods (I believe that was the point James was making in the article).

Dan Howard said...

Let's say we have a large team.

Teams change over time. People come and go - including the architects.

So in your large application a defect is reported and one of your noob developers sits down to solve it.

He finds:

Customer c = getCustomer(123)
c += 10

So now in order for him to understand what he might need to change here he not only needs to understand the current code that he's in but he also needs to understand the Customer object.

So he looks a Customer and finds more / % ^ * & # 'operators' on other objects so now he has to understand those. And so on - and so on....

I can't speak for Scala but this particular language feature of SmallTalk was largely what ended its use in large applications back in the 90s.

SmallTalk was a bit of a worst case because it required operator overloading at it's core because of it's 'everything is an object' design.

Additionally, considering methods can (depending on language) throw exceptions, take variable number of params or take params BY value or BY ref it can get even more messy.

customer2 = customer + 5

Was customer modified by this code?

It seems like a lot of potential problems for something which really amounts to a bit of language sugar candy.

James Iry said...

@Dan,

Let's say we have a large team.

Teams change over time. People come and go - including the architects.

So in your large application a defect is reported and one of your noob developers sits down to solve it.

He finds:

Customer c = getCustomer(123)
c.plusEquals(10)

So now in order for him to understand what he might need to change here he not only needs to understand the current code that he's in but he also needs to understand the Customer object.

So he looks a Customer and finds more divide, mod, xor, times, and hash 'methods' on other objects so now he has to understand those. And so on - and so on....

Additionally, considering methods can (depending on language) throw exceptions, take variable number of params or take params BY value or BY ref it can get even more messy.

customer2 = customer.plus(5)

Was customer modified by this code?

It seems like a lot of potential problems. Period.

Daniel Spiewak said...

...so the moral of the story is as follows:

Programmers have a negative effect
on software development.

Dan Howard said...

@James Iry.

No you've arbitrarily assigned # to mean hash and + to mean plus etc. Those meanings are irrelevant.

What I'm saying is that these could have any meaning.

In my example I was thinking c += 10 meant c.adjustAccountBalance(10) but it could have meant anything c.setDefaultDaysDue(10) c.adjustLastDateOfInvoiceByDays(10) etc. etc.. etc...

You've adroitly demonstrated the problem.

Thanks.

Daniel Spiewak said...

@Dan

There are certainly times where operator overloading is inappropriate. I think these situations are seen more often in Scala than other languages, but that's really beside the point. Good API design requires moderation in the techniques employed. I am by no means anti-named-methods. I simply believe that custom operator definitions are a sufficiently useful technique as to merit inclusion in a language, just as arbitrary method names can be just as useful in different circumstances.

Daniel Spiewak said...

To be clear, what I mean by "seen more often in Scala" is that a lot of people are coming from Java (which has no operator overloading) to Scala (which has super-powerful operator overloading). As a result, they tend to abuse the feature, employing it for some things which really merit an alpha-numeric method name.

James Iry said...

@Dan,

Let's say we have a large team.

Teams change over time. People come and go - including the architects.

So in your large application a defect is reported and one of your noob developers sits down to solve it.

He finds:

Customer c = getCustomer(123)
c.froggleTheSnorf(10)

So now in order for him to understand what he might need to change here he not only needs to understand the current code that he's in but he also needs to understand the Customer object.

So he looks a Customer and finds more fribjub, lorglelbast, qwertysnuff, frizzlebait, and humpthewumpus 'methods' on other objects so now he has to understand those. And so on - and so on....

Additionally, considering methods can (depending on language) throw exceptions, take variable number of params or take params BY value or BY ref it can get even more messy.

customer2 = customer.megasnarf(5)

Was customer modified by this code?

It seems like a lot of potential problems. Period.

--------------------------------------
Okay, maybe you don't like giberish. That's cheating somehow. Let's try again with "meaningful" names

Let's say we have a large team.

Teams change over time. People come and go - including the architects.

So in your large application a defect is reported and one of your noob developers sits down to solve it.

He finds:

Customer c = getCustomer(123)
c.increaseCreditLimit(10)

So now in order for him to understand what he might need to change here he not only needs to understand the current code that he's in but he also needs to understand the Customer object.

So he looks a Customer and finds more calculateTotalPurchases, resetTaxRate, retrieveInvoice, and clone 'methods' on other objects so now he has to understand those. And so on - and so on....

Additionally, considering methods can (depending on language) throw exceptions, take variable number of params or take params BY value or BY ref it can get even more messy.

customer2 = customer.servicePriority(5)

Was customer modified by this code?

It seems like a lot of potential problems. Period.

Now, why would you think there's any less likely-hood of errors hidden by "nice" names in this version than any of the others? Just because the names read like English doesn't guarantee that they say anything about their real semantics - either intended or actual.

James Iry said...

Let me clarify as well. I think that + is very, very rarely the right name for something. I can agree with all the "what about abuse" folks on that front. But I also think "changePassword" is very, very rarely the right name for something. Of all the code I've ever seen in my life, changing passwords constitutes only a tiny fraction. Naming something "changePassword" or "+" is going to be totally and utterly wrong the vast and overwhelming majority of the time. The exceptions are, of course, when one is exactly the right name.

Conal said...

@Daniel Spiewak: I agree. Expanding: learning (education) is the cure; resistance to learning is the illness, and complaints of "confusing" is the symptom. Progress is often confusing. That confusion might be indicating it's time to expand one's understanding. I imagine the idea of a non-flat world was pretty darn confusing for a lot of people.

By the way, "confusing" is not a property of a thing in itself. It's a relationship between a thing and a mental state. When viewed as such, more possible reactions suggest themselves. To say "I'm confused about it", rather than the less accurate (and more popular) "It's confusing", is to take responsibility for one's own mental processes and to empower oneself to grow.

@Dan Howard: When reading your replies, I don't get the impression that you've taken in what James has been saying about operator-style surface notation compared with alphabetic-style notation. There's just a bit of subtlety in James's responses that may be worth another read or two.

Anonymous said...

Unfortunately, this argument is nothing more than a linguistic trick. It works by doing an illegal substitution. Taking a sentence and replacing the character "+" by an arbitrary string registers as a type mismatch in my linguistic parsers. Let me say that in plain English: while I would agree with the claim that:
"Somebody will name something '+' when it has nothing to do with the common notion of '+'" can be viewed as "Somebody will name something [a single character] when it has nothing to do with the common notion of [a single character]" , I will not agree to an arbitrary substitution. In other words, the conclusion: "In other words, programmers simply shouldn't be allowed to name things *to a single character*" is perfectly valid, but not "In other words, programmers simply shouldn't be allowed to name things *to a nice descriptive name*". I can phrase it even more succinctly: programmers must always chose a descriptive name when they name something and a single character is never descriptive.

Setting the badness of the argument aside, I don't understand why people refuse to learn from past history. Let me demonstrate: what does "x << 5" mean to you? In C and Java, it's unambiguous: x is some integer type and it's being left shifted by 5. In C++, it could mean anything. In fact, you can't write the "Hello world" program without running into this particular abuse. If somebody asked me what I would name a method that wrote to a character stream, I'd probably say "write" or maybe "print". How the frack did "write" turn into "<<"? And that's just the tip of the iceberg. There are dozens of classes in the *standard* C++ libraries that do goofy things like this. This is from the people who wrote the language. They seem to purposely encourage bad operator overloading. If they didn't understand the badness and screwed it up so thoroughly what chance does an average developer have? Just look at the C++ FQA on operator overloading to get a glimpse of the nightmare (http://yosefk.com/c++fqa/operator.html).

James, you really did lose the argument when you substituted bad versions into Dan's post. It demonstrated perfectly that a single character method name has no hope of being descriptive. At least a nice English word (or phrase) has a chance of being meaningful.

James Iry said...

- Let me demonstrate: what does "x << 5" mean to you?

Note that you argued against one character symbols but that's a two character symbol. Still, that's a quibble.

Let me bring it back home. Since you mentioned Java we'll go with that.

What does c.setPassword("blah") do? Does it
a) change the password on an in memory representation of a user?
b) change the password on a persistent database representation of a user and commit the transaction?
c) change the password needed to log in to the current system?
d) change the password that will be used to log in to a remote system?
e) Something totally unrelated to password changing?

If you're honest with yourself you'll admit that any of the above are possible. e is perhaps unlikely, but that's only because you expect people to name things reasonably, because others review our code (even if only very informally), etc. Still, it's a possibility and you have to account for it in any attack against operator abuse.

Even if you ignore case e as a possibility, a through d are all substantially different meanings for "changePassword" and the only way you can know what "changePassword" does is track it down. "changePassword" isn't descriptive enough to tell you all that you need to know - at best it's a mnemonic.

Further, if continue to be honest with yourself, c.setPassword("blah") could mean several of those things in the same system depending on the context and value of c. It could be ad-hoc overloaded in the sense that two unrelated hierarchies have defined it. And it can be overridden in the sense that different classes in the same hierarchy have overridden it. That, after all, is core to what most people call OO (encapsulation, inheritance). So you might very well have to learn several different meanings for "changePassword" in one system.

Now what you've brought to me is an argument that << "means" left shift. Let's go with it. Let's say, for the sake of argument, that I agree with you 100% and say that stuff like the iostream usage is an abomination. "<<" is the left shift operation, period, end of discussion.

Okay...so what if I write a bignum (aka BigInteger) class - an arbitrary capacity integer. Such a thing is clearly useful in some domains - it's in the Java standard library, after all. And, hey, it's an integer type so why not have left shift? But what should I call the left shift operation? I really shouldn't call it "leftShift", that doesn't follow the convention we just agreed to! I'm doing the wrong thing if I don't call it "<<"! If I call it "<<" I'm doing exactly the right thing. We agreed!

While I'm at it, what's "non-descriptive" about naming the addition operation on BigInteger "+" - it does addition after all. In fact, it does addition better than the silly fixnums we use all the time since those little bastards overflow.

Conclusion: don't call things << unless, by some "reasonable" measure they "mean" what people expect from "<<". Call things "<<" when they are "<<". I'm not going to define what that meaning is exactly - you and your team can think iostream's usage is brilliant or an abomination.

Or maybe you use Haskell and << means sequence two monadic computations and ignore the result on the left. The type system will even enforce that. But wait, what exactly does a monadic computation do? Well, within a few bounds, that's a bit overloaded.

Daniel Spiewak said...

@Joe

The whole argument really is about which name is more descriptive. Consider the following two examples:

class OutputStream {
def write(c: Character) {
...
}
}

case class Complex(a: Int, b: Int) {
def +(c: Complex) = ...
}

Which is a better name for the OutputStream method: `write` or `<<`? I would tend to say `write`, just because the intention is much clearer (as you pointed out). However, what about the Complex method: `+` or `plus`? Which is more descriptive in that case?

Let's look at a less pedestrian example:

abstract class Monad[+A] {
def >>=[B](f: A=>Monad[B]) = ...
}

Alternatively:

abstract class Monad[+A] {
def bind[B](f: A=>Monad[B]) = ...
}

Which is better: `>>=` or `bind`? Technically, they mean the same thing, but I find >>= easier to read and more clearly descriptive of the method's intent.

There is no one blanket rule which covers all situations, just like there is no *single* best practice which governs API design. The point is that a judicious use of operator overloading can be extremely beneficial. Simply claiming that *all* operator overloading is detrimental is a naive statement at best.

Also, I would avoid using C++ as an example of a language with operator overloading. As James pointed out in the article, and as I mentioned in an earlier comment, C++ has a *horribly* crippled implementation of operator overloading. Scala or Haskell make much better candidates for argument, and to a lesser extent Ruby or Smalltalk. In C++, operators are special creatures that are conceptually and practically distinct from normal functions. In Scala and Haskell, operators are exactly as they were mathematically intended: nothing more than infix functions. This definition of operators is far more useful and far less problematic.

Anonymous said...

I think we could continue this until we're all blue in the face, so for better or worse, this is my last comment.

@James:

Although you didn't say it explicitly, it seems to me that the thrust of your argument does not address the first paragraph of my original post. (It's quite interesting that you start by picking on my second paragraph.)

On the whole setPassword thing: I agree that it's possible for setPassword to mean descriptions a-d, or variations thereof. There is something immutable about a-d: they all start with "change the password". That's really important. If I'm looking through a code base with 10K classes and 1M lines of code, I'm either looking for something that changes a password, or I'm not. In the former case, I'll happily dig into the setPassword method and fully understand its semantics. In the latter case, if I run across setPassword("blah"), I'll ignore it as a first pass, which is an enormous help.

The thing about setPassword that's very different than "+" is that the set N-letter English phrases is infinitely larger than 1-2 character operators. While it's possible that setPassword has several different meanings in a code base, with operator overloading, "+" will have many more. Practical example: if we grep "operator+" in the C++ standard library sources, we'll get many hits, many of which are abusive in one way or another. How many hits will be get if we grep for "setPassword"?

Your BigInteger arguments now fall into the classic cases of "operator overloading is good because number types work well". Nobody is going to disagree with that, not even Gosling. But language design is about reasonable compromise. I'll take "leftShift" over "<<" if it prevents the abomination that is "cout << 5". BTW, I never said anything about using reasonable synonyms. leftShift is just as descriptive as "<<". The argument is about descriptiveness, not sameness.

@Dan:

> what about the Complex method: `+` or `plus`? Which is more descriptive in that case?

Isn't it obvious? They are equally *descriptive*. Clearly, "+" is shorter.

Sorry, I can't opine on "bind", other that not knowing what it means, I might actually prefer "bind" to ">>=", because, like it or not, ">>=" has been beaten into my head in the tradition of C.

It's also not fair to exclude C++ from the discussion. C++ is the closest language to Java, much more so than Haskell or Scala. Bruce's original article was about Java, after all. The abominations that C++ has produced via operator overloading are more likely to occur in Java.

Daniel Spiewak said...

I was actually trying to choose an operator which was invalid in C++. I forgot that >>= is the "right-shift assign" operator. Oh well...

There are other examples to choose from, like Scala's collection concatenation operator (a ++ b), or even more esoteric applications like parser combinators (a ~ b ~> c). My favorite example is actually list prepend, which is conventionally done using the cons operator (a :: b). The point is that there are cases where operators are preferable to named methods.

Bruce's post is about Java, but it touches on the larger issue of operator overloading as a language feature. Bottom line: do I think Java should get operator overloading? Absolutely not. I don't think there is a way to implement it in Java without ending up with something approaching C++. However, I still strongly believe that when the language feature is *properly* implemented (Haskell, Scala, etc) it can be a powerful enablement for more fluid APIs and for cleaner code. As with any language feature, it can be abused, but it is no more or less prone to that abuse than any other function-related feature.

The major reason to exclude C++ from this debate is what I said earlier: C++ does not have a full-fledged mechanism for operator definition. Note that I'm making the distinction between "definition" and "overloading". In C++, operators are not normal functions. They have a different syntax (prefix, infix or postfix with no dot (.) or arrow (->)) and they must be declared in a different way. In fact, operators cannot even be defined as member functions without crippling their associativity (implicit conversions cannot be applied to the left operand when the operator is a member function). All in all, C++ operators are primitive features and thus second-class citizens of the language. In order to truly examine the merits or demerits of operator definition as a language feature, we need to look at a first-class implementation, something that C++ does not provide. Basing arguments for or against operator overloading on the C++ implementation only leads to misconception and confusion (see Martin's example from earlier).

James Iry said...

@Joe,

I don't need to take care of your first paragraph. You already did. The conclusion of your first paragraph was

> programmers must always chose a descriptive name when they name something and a single character is never descriptive.

Fortunately, you recanted later when you said

> "operator overloading is good because number types work well". Nobody is going to disagree with that,

I say "fortunately" because the paragraph I didn't address assumed its conclusion in order to reach its conclusion. At least I think that's what it did. It was very hard to follow the reasoning.

Anyway, in the rest of your last response you make essentially the same error. You say that + is overloaded a lot on the C++ library but setPassword is not. Okay, but how 'bout every other possible identifier? You'll need a list of all the names used in C++ to prove that + is more abused - you can't just look for setPassword.

Still, even if, for the sake of argument I agree with you and say that + is abused in C++ a lot more than other names that doesn't automatically mean that the best fix is by preventing operator overloading. Perhaps the best fix is education.

Finally, I can assure you that in Haskell's standard library + is only used for number like things and that in Scala's standard library it's only "abused" once, and that is as string concatenation to appease Java programmers who expect "foo" + "bar" to be "foobar" (question: when an operator is so frequently abused that people expect it, is it still abuse?).

Let me sum up: you've argued against C++'s model based on an assumed empirical result that does not hold under other languages and concluded the most draconian solution is the only one.

Anonymous said...

Elliotte Rusty Harold makes an excellent point in http://cafe.elharo.com/blogroll/operator-overloading/: there are really only relatively few situations in which operator overloading makes sense. He advocates dropping the feature altogether, but I don't agree: in those few situations, it can be very powerful to be able to use operator overloading.

However, I think books like Programming in Scala should do much more to discourage its use, instead of boasting it as one of the powerful features of the language. I hope I can convince my company to allow the use of Scala, but I don't think I would ever have any use for operator overloading and I would probably chastise any colleague that used it.

James Iry wrote:
Naming something "changePassword" or "+" is going to be totally and utterly wrong the vast and overwhelming majority of the time.
However, if one is wrong 99% of the time, while the other is wrong 99.99% of the time, than the first is still 100x more often right than the latter. In my life, I've written several methods that could rightfully be named 'changePassword': the name would give a reasonable idea of what the method does and whether someone reading the code wanted to read the javadoc, comments or implementation. However, I've never written a method that could have sensibly been named '+', '>>' or '#!'.

Bruce Eckel said...

Excellently argued, but you didn't take it far enough -- the general problem is saying "we must prevent programmers from doing X because they might abuse it." See Martin Fowler's description of "Directing vs. Enabling" http://www.martinfowler.com/bliki//SoftwareDevelopmentAttitude.html

James Iry said...

@confusion

Really? You've never written a currency type? A unit of measure type? Ever? Your code is a bunch of naked numbers running around with no attempt to make sure you're not adding euros to miles and subtracting milliseconds?

I've read of such code. Is it as terrifying as it sounds?

Anonymous said...

@James

There simply aren't that many numbers running around in my code. There is hardly a risk of mixing euros with miles or milliseconds. Proper variable names and the decimal/double distinction are enough to prevent the occasional measurement of time to interfere with the adding of some monetary amounts. I also mean that add() has never been a proper methodname for any of my methods. FooList.addFoo() perhaps, but not a naked add().

Had I been a scientific programmer or working for a technology company, I probably would've used methods named 'add()' and would have been yearning for operator overloading or '+' as a valid method name. However, I think I speak for the vast majority of software engineers when I say that I've never had a use for such things.

Eric said...

@confusion:
"However, I think I speak for the vast majority of software engineers when I say that I've never had a use for such things."

As long as that "vast majority" excludes anyone that works in finance, insurance, payroll, analysis, science, or simulation, I'd be inclined to agree with you.

For the negligible (!) few that work in any of those fields, the fact that we can't call numerical operators by their proper names (at least in Java) is a major pain in the ass with no workaround, and severely detracts from the readability (and debuggability) of mathematical code.

If the right approach to operator names is to disallow anything that might be abused, let's start by blacklisting doSomething() and foo() - I've seen more use of those two in live code than I have people using an overloaded "+" to mean anything other than addition...

I mean, I can't count one time in real code that I've had to name a method doSomething, so it can't be that important that people are allowed to, right?

Aapje said...

@eric,

If you want to write a serious amount of math code (where operator overloading would come in handy), you should really use a language suitable for that domain. An example is Fortress. No language can be good for everything.

Aapje said...

@james,

Your entire argument is flawed because there is a difference between base functionality and syntax sugar. Without named methods, Java would be a non-functional language. Without operator overloading, you have a perfectly usable language. Secondly, operator overloading is useful only rarely. So you cannot simply state that the arguments against overloading are wrong because those arguments can be made against methods as well.

A language designer doesn't rate a potential new feature based the flaws it shares with existing features. It is rated based on the value it has compared to the downsides. Every new feature adds complexity to the language and thus starts in the hole. I don't see how operator overloading provides highly useful functionality for the majority of programmers who do basic math. If you want to do complex math, Java is not a very good choice even if it had (good) operator overloading.

Aaron said...

@Appje:

The thing is, "operator overloading" doesn't have to be a special feature that one weighs the merits of.

Instead, one should consider whether and why there should be support in one's language for method names that are (a) non-alphabetical, and (b) invoked with whitespace rather than a customary OO dot.

If you decide that you're going to add /this/ feature, the next question is whether you're going to go out of your way to support it only for some limited set of built-in classes, or whether you'll just go ahead and build a general mechanism that you make use of for your built-in types.

Which is to say: Scala does not "support operator overloading." It just happens not to mind if you name a method '+' instead of 'plus', or if you invoke your methods as

a.plus(b)

or

a plus b

or

a.+(b)

or

a + b

So there's a reasonable sense in which it has made the simple, default decision, and weirdo languages like Java have put in some extra effort to do something special-cased and peculiar.

Aapje said...

@Aaron,

You are approaching this issue purely from the standpoint of a language designer, which (for a non-academic language) is secondary to the programmer perspective. For a regular programmer, operators are not the same as regular functions. For one, they use symbols instead of text. There are many examples of GUIs that use undecipherable icons to represent concepts, but programmers keep using those crappy icons in their applications. Those icons are far more expressive than an operator, unless the operator already has a clear prior meaning. However, most meaningful operators are already used in Java, so what would be gained by allowing the definition of other operators? I bet that >50% of the uses of that feature is what I consider abuse. I see some justification for just allowing the overloading of existing operators, mostly for wrapper classes. However, even there the potential for abuse is substantial.

Your entire post ignores the real issue, which is not whether a clean implementation is possible. Java is not a clean language, which made it a success in contrast to much cleaner languages such as Smalltalk and Lisp. Java succeeded because it was a pragmatic language that allowed regular programmers to write decent, maintainable code at a good pace. If it had been a clean, ultra-powerful language, it would have failed. Even as it is, there is plenty of crappy code being written. When maintaining code, you have to understand every line of code and all the interactions quickly. I've encountered bugs that crashed or required us to take down important web-applications. At that point, you don't want clever code. You want understandable code that you can debug quickly, so the millions of dollars keep coming in or the boss can save face.

Rev. Zak Zennii said...

Personally, I'm a fan of operator overloading, and not a fan of denying something because of potential for abuse.

Aside from the usual complex numbers and matrices (both of which I've overloaded operators for, I have also used overloading to streamline code involving color manipulation. Now, before anyone cries foul, I'm using the operators in a sensible way:

chroma3 = chroma1 + chroma2
is the same as
chroma3.X = chroma1.X + chroma2.X
chroma3.Y = chroma1.Y + chroma2.Y
chroma3.Z = chroma1.Z + chroma2.Z

and

chroma2 = 0.75*chroma1
is the same as
chroma2.X = 0.75*chroma1.X
chroma2.Y = 0.75*chroma1.Y
chroma2.Z = 0.75*chroma1.Z

I find that the code I can write with overloaded operators is cleaner and easier to read, and much faster to write than code without it.

As for the argument that people will abuse the feature -- yes, some will, but by the same token, some people will plug too many things into a power strip and start fires. And it sure would be a hassle if those weren't available.

Now, I admit, I use a high-level language (I won't say which one, for fear of being laughed off the board), and I place a very high value on ease of writing and understanding my code. I place a much lower value on keeping up assumptions about what's going on behind the scenes.

And J Suereth: Good one about beating imprudent developers with raw halibut! And if you're short on halibut, the keyboard works nicely too :P

Anonymous said...

Too many lazy programmers that do not want to learn how to properly use the features in c++. Stick with your Java, be a one pony show.

Anonymous said...

C++ operator overloading defies common sense and code maintenance metrics in a huge way. Both operator overloading and function overloading should be avoided at all costs.

Both are overly complicated, prone to confusion and error and can be coded more simply in other ways such as using the basic language features for functions, classes and structures.

I know how clever some people want to be and how the ego comes into play, overriding the axioms of writing legible and maintainable code. You can not wait to show the world your intricate overloaded c++ programs. Please do it in the confines of your own home. My advice - don't do it. Especailly not on my projects or you will be reamed, steamed, dry cleaned and if needed, taken out back and beaten into submission.

The industry would do best to eliminate these and other non-sense features of the c++ language and that would give c++ a better name in the industry.

Conal said...

Anonymous said: "C++ operator overloading defies common sense and code maintenance metrics in a huge way. [...]"

"Common sense" has never been a compelling argument for me. After all, progress is the ongoing overturning of common sense.

I'm intrigued with the mention of metrics. What metrics do you mean? (References, please.) And how to reconcile the sense against overloading with the sense in favor?

Do you really want to take away the operator overloading already present in C?

A plea for rational discussion & exploration:

Many specific points have been expressed in the discussion above. For the sake of progress, I'd appreciate additional contributions taking these points into consideration and building on them, integrating the reasoning (as opposed to opinions) into a whole. As Henry Nelson Wieman put it, "To get the viewpoint of the other person appreciatively and profoundly and reconcile it with his own so far as possible".

Anonymous: willing to try again?

Thanks!

Vlad Patryshev said...

I believe now that overloading of '+' in c++ means just that we have a commutative monoid. (well, in Java it is not always commutative :)

Anon said...

Though this one's a year old I have to comment on it...

Asociativity for sure IS a problem when we talk about overloading infix operators. Well there's been this answer
which look like a joke:

"@Mohamed,

Should 'plus' always be associative?"

For sure this is no analogy at all; plus would be a function call and thus prefix(!). There is no ambiguity in
plus(a, plus(b,c)), but in a+b+c there *is* one.

Languages have to deal with this problem by either restricting evealuation to left asociativity (or right as.) which in fact would turn it useless for most of the ways I'd like to use that feature. Or it would have to forbid multiple occurences of the operator without the use of parentheses:
(eg. not a + b + c but (a+b)+c and the former would make the compiler cry).

NXTangl said...

That's a failure of expressiveness, not an argument against it.

sealed abstract class Truth {
abstract def ||(=> Truth):Truth
abstract def &&(=> Truth):Truth
}

case class True {
def ||(_ : => Truth) = this;
def &&(arg : =>Truth) = arg;
}

case class False {
def &&(_ : =>Truth) = this;
def ||(arg : =>Truth) = arg;
}