_This issue was originally filed by lsegal...@soen.ca_
I saw mention in documentation that Dart does not support method overloading because it is a dynamic language. However, Dart does support optional type annotations, which, as far as I can tell, opens the door slightly for the possibility of having method overloading when using typed argument values. I am proposing support for this behavior.
Currently when a programmer wants to support multiple types in a dynamic language using a single method, they will perform the instance checks manually as follows:
class Displayer {
void display(element) {
if (element is String) { print(element); }
else if (element is Image) { /* display image on screen */ }
else { throw "Invalid argument"; }
}
}
This is cumbersome to the programmer and not easily translated in documentation-- not to mention easy to get wrong. Given support for optional types, I think we should be able to offload this dispatch to the compiler, which would make it much easier to write such methods.
For example, with overloading we could simplify the above snippet to:
class Displayer {
void display(String text) { print(text); }
void display(Image image) { /* display image on screen */ }
}
I am no compiler expert, but it seems plausible to me that given type annotations, a compiler could easily handle dispatching to the correct method at runtime. In addition, when cross-compiling to JS, the compiler could insert the manual checks as per the initial snippet above so that it would be equivalent to existing code that we see today. In fact, in the worst case, the Dart compiler could just always perform this translation directly in the method when it sees such an overload. That way there would not even be a need to modify the method dispatch rules in the VM-- it would simply be a completely transparent type check / dispatch that the user no longer has to do manually.
A couple of obvious restrictions on the functionality would be:
All in all, this would be a great way to display the power of optional typing in the dynamic language world. It simplifies an idiom that is commonly used in dyn langs to circumvent the lack of overloading, and it would save developers from having to use separate method names depending on the input types. What do you think?
_Removed Type-Defect label._
_Added Type-Enhancement, Area-Language labels._
_This comment was originally written by [email protected]_
I don't see how this fits with having program behavior be unchanged by removing type annotations. Also, if I call foo.display(x) where I don't know the type of foo or x, how do I know what method to call? It seems this would require creating a trampoline method which does runtime type checks of its arguments and relays to the proper implementation.
In most cases, overloading is easily avoided by naming the methods appropriately. For example, name your methods displayString and displayImage -- if you know the type of the argument statically, you can easily choose the correct one. In most other cases where overriding would be useful, named/optional parameters covers the case well.
_This comment was originally written by lsegal...@soen.ca_
The dispatch would of course use runtime checks on the type. But again, if there are restrictions at the VM level to not be aware of type information, this can be pre-compiled into a "trampoline" method, as you put it, as per the snippet I listed at the top. I'm not sure about having program behaviour remain unchanged by removing type annotations, but it seems like a reasonable exception to me. It's simply a shorthand syntax to performing the manual dispatch with is-a checks, so like I said, it should be intuitive enough to the user as to why this situation is different.
As for type-named methods, part of the elegance of overloading is that the user need not care what they are "display"ing. The use of separate method names still means that I need to dispatch manually at some point. It may even require is-a checks, depending on the API. I'm in favour of keeping the verb count of a class down, and type annotations allow for this possibility.
It's important to note that many existing JS libs agree with this ideology, jQuery being one of the most popular, of using single verbs for many types [1]. If jQuery were to be ported over to Dart, it would likely still use the same API concepts and perform the dispatch manually based on type as I've shown rather than change the API to use type-named methods. In fact, using type-named methods would basically defeat the practicality of jQuery to begin with, so I don't even see that as an option. The point of this feature would be to reduce the friction for implementing real-world APIs like jQuery's. Optimizations aren't really an issue, because this stuff is going to happen whether it's supported natively in Dart's syntax or not. That said, there is also a possibility of some minor optimizations if this were supported at the VM-level (you could probably short-circuit and potentially cache the is-a checks, but again, I'm no compiler expert).
_This comment was originally written by [email protected]_
jQuery returns different types depending on the argument, so even your proposal wouldn't make a direct translation of jQuery possible while keeping full type information. That is one of the problems Dart is trying to solve how can an IDE possibly know what sort of things you can do with the result of a particular invocation of the $() function?
_This comment was originally written by lsegal...@soen.ca_
Well this proposal isn't meant to solve the return types issue, that's a separate one altogether, and a little out of the scope of overloading. It also doesn't affect how overloading would work. As mentioned, one of the requirements would be the same return type, which is common for implementations of overloading-- jQuery would presumably use dynamic for that return type. To be honest, I'm not really concerned about how an IDE would find the return type of such an overloaded method. It's hard either way, and it's not what this proposal is meant to solve.
FWIW the method linked to above (the $() method) always returns a jQuery object regardless of arguments, so your argument doesn't apply to that specific case-- overloading would work perfectly fine there, not that it wouldn't even if the return type was variant.
_This comment was originally written by lsegal...@soen.ca_
I should also point out that a recent mailing list topic about operator overloading proves for a really good use case for this kind of a thing:
https://groups.google.com/a/dartlang.org/group/misc/browse_thread/thread/3c666afe4390ba1e
Operator overloading is limited to a single "verb" (or operator), so you have no choice but to either use the single named method and perform manual is-a checks, or break out of your API and avoid operators altogether. The first is error prone, the latter leads to unintuitive and inconsistent APIs. Even the idiomatic usage of operator overloading for multiple types would force you into this manual instance checking, so yes, this kind of code really exists, and there are cases where overloading would simply be far superior.
There will be no type based overloading in Dart. If you need a different variant of a method, create a method with a different name. This is what anyone using a dynmaic language does anyway and they are better for it. Even in language with mandatory types, type-based overloading is a bad idea, creating brittleness and ambiguity.
_Added WontFix label._
_This comment was originally written by lsegal...@soen.ca_
Hi Gilad,
Thanks for your comments, but I don't think you were following in on this discussion.
"This is what anyone using a [dynamic] language does anyway and they are better for it"
This is patently false and highly subjective. What is your standard for "better"? I would think the better APIs are ones that I don't need to think about when having to pass in an argument. "Is it display() or displayImage()? Let's go check the docs again". Are you really suggesting that even method overloading in static languages are a bad idea? I've never heard of this argument before. If you've ever overloaded a single operator or implemented one visitor pattern, you must certainly be aware of the real world applications of overloading.
As for the false claim that nobody is doing this, jQuery does, and I think that's enough to disprove the claim. The most popular library (like, inordinately popular) in a dynamic languages makes heavy use of simulated overloading on multi-type methods and nobody is doing it? I've seen simulated overloading in just about every dynamic language I've ever come across. http://codesearch.google.com/#search/&q=%5C.is_a%5C?%20lang:%5Eruby$&type=cs gives you a good idea of its pervasiveness in Ruby, for example. The first 3 pages of results for that simple generic is-a method call check are almost exclusively used for manual method dispatch to simulate overloading. So pervasive, in fact, that it is even found in the stdlib. It's a little harder to do this kind of a search in python but even http://codesearch.google.com/#search/&q=%22is%20str%22%20lang:%5Epython$&sq=&type=cs for strings shows a couple of results for that language. I could go on, but I think the point has been made.
I should finally point out (and reiterate, in case you had not fully read the previous posts) that even Google employees seem to suggest that manual is-a dispatching seems to be okay, as per the GWT employee writing in the operator overloading thread linked above. There are also legitimate scenarios where you have no choice but to simulate overloading (as in operator overloading for multiple types-- unless you are seriously saying not to use operators for this behaviour, which would defeat part of the elegance of having operators in the language in the first place). This suggests to me that even Google's own dart codebase will at some point contain these dispatches. I'm not sure if you're just denying the existence of this huge amount of existing dynamic language code, or if you legitimately have never come across this before-- but I really can't imagine it being the latter.
Dart is going to be no different than these other languages. You will be seeing this kind of code whether you want to acknowledge it or not. The decision now is whether you want to help these programmers make existing idioms simpler to use or try to unsuccessfully bury the idiom by pretending it does not exist. It's unfortunate that the latter is being chosen.
_This comment was originally written by hugo6fer...@gmail.com_
Any chance of reconsidering this?
Would it also be possible to consider operator overloading?
Simply makes reading and maintaining code much easier.
_This comment was originally written by rgi...@gmail.com_
I understand why the developers of Dart want to allow for untyped coding. They want JavaScript coders to adopt Dart. But I think method overloading should be included and typed variables should be mandatory. Here is an example.
class Point {
num x; //x-coordinate
num y; //y-coordinate
num z; //z-coordinate
Point() { //default
this.x=0; //set x
this.y=0; //set y
this.z=0; //set z
} //end Point default
//1
Point(num: x, num: y, num: z) { //cartesian
this.x=x; //set x
this.y=y; //set y
this.z=z; //set z
} //end Point cartesian
Point(num: r, num: theta, num: z) { //cylindrical coordinates
this.x=rcos(theta); //set x
this.y=rsin(theta); //set y
this.z=z; //set z
} //end Point for cylindrical coordinates
Point(num: p, num: theta, num: phi) { //spherical coordinates
this.x=psin(phi)cos(theta); //set x
this.y=psin(phi)sin(theta); //set y
this.z=p*cos(phi); //set z
} //end Point for spherical coordinates
//2
Point(double: x, num: y, num: z) { //cartesian
this.x=x; //set x
this.y=y; //set y
this.z=z; //set z
} //end Point cartesian
Point(double: r, num: theta, num: z) { //cylindrical coordinates
this.x=rcos(theta); //set x
this.y=rsin(theta); //set y
this.z=z; //set z
} //end Point for cylindrical coordinates
Point(double: p, num: theta, num: phi) { //spherical coordinates
this.x=psin(phi)cos(theta); //set x
this.y=psin(phi)sin(theta); //set y
this.z=p*cos(phi); //set z
} //end Point for spherical coordinates
//3
Point(num: x, double: y, num: z) { //cartesian
this.x=x; //set x
this.y=y; //set y
this.z=z; //set z
} //end Point cartesian
Point(num: r, double: theta, num: z) { //cylindrical coordinates
this.x=rcos(theta); //set x
this.y=rsin(theta); //set y
this.z=z; //set z
} //end Point for cylindrical coordinates
Point(num: p, double: theta, num: phi) { //spherical coordinates
this.x=psin(phi)cos(theta); //set x
this.y=psin(phi)sin(theta); //set y
this.z=p*cos(phi); //set z
} //end Point for spherical coordinates
//4
Point(num: x, num: y, double: z) { //cartesian
this.x=x; //set x
this.y=y; //set y
this.z=z; //set z
} //end Point cartesian
Point(num: r, num: theta, double: z) { //cylindrical coordinates
this.x=rcos(theta); //set x
this.y=rsin(theta); //set y
this.z=z; //set z
} //end Point for cylindrical coordinates
Point(num: p, num: theta, double: phi) { //spherical coordinates
this.x=psin(phi)cos(theta); //set x
this.y=psin(phi)sin(theta); //set y
this.z=p*cos(phi); //set z
} //end Point for spherical coordinates
//5
Point(double: x, double: y, num: z) { //cartesian
this.x=x; //set x
this.y=y; //set y
this.z=z; //set z
} //end Point cartesian
Point(double: r, double: theta, num: z) { //cylindrical coordinates
this.x=rcos(theta); //set x
this.y=rsin(theta); //set y
this.z=z; //set z
} //end Point for cylindrical coordinates
Point(double: p, double: theta, num: phi) { //spherical coordinates
this.x=psin(phi)cos(theta); //set x
this.y=psin(phi)sin(theta); //set y
this.z=p*cos(phi); //set z
} //end Point for spherical coordinates
//6
Point(double: x, num: y, double: z) { //cartesian
this.x=x; //set x
this.y=y; //set y
this.z=z; //set z
} //end Point cartesian
Point(double: r, num: theta, double: z) { //cylindrical coordinates
this.x=rcos(theta); //set x
this.y=rsin(theta); //set y
this.z=z; //set z
} //end Point for cylindrical coordinates
Point(double: p, num: theta, double: phi) { //spherical coordinates
this.x=psin(phi)cos(theta); //set x
this.y=psin(phi)sin(theta); //set y
this.z=p*cos(phi); //set z
} //end Point for spherical coordinates
//7
Point(double: x, double: y, double: z) { //cartesian
this.x=x; //set x
this.y=y; //set y
this.z=z; //set z
} //end Point cartesian
Point(double: r, double: theta, double: z) { //cylindrical coordinates
this.x=rcos(theta); //set x
this.y=rsin(theta); //set y
this.z=z; //set z
} //end Point for cylindrical coordinates
Point(double: p, double: theta, double: phi) { //spherical coordinates
this.x=psin(phi)cos(theta); //set x
this.y=psin(phi)sin(theta); //set y
this.z=p*cos(phi); //set z
} //end Point for spherical coordinates
//8
Point(num: x, double: y, double: z) { //cartesian
this.x=x; //set x
this.y=y; //set y
this.z=z; //set z
} //end Point cartesian
Point(num: r, double: theta, double: z) { //cylindrical coordinates
this.x=rcos(theta); //set x
this.y=rsin(theta); //set y
this.z=z; //set z
} //end Point for cylindrical coordinates
Point(num: p, double: theta, double: phi) { //spherical coordinates
this.x=psin(phi)cos(theta); //set x
this.y=psin(phi)sin(theta); //set y
this.z=p*cos(phi); //set z
} //end Point for spherical coordinates
} end class Point
I do not think optional parameters is a solution. This method may need to take in values in inches and meters, for example.
I may have inputs in different scales in US customs units or metric units (like feet vs. inches, or meters vs. milimeters.
I want my constructor to take care of all these cases. Overloading functions is elegant. If you have to take away anything from
Dart I'd suggest scrapping overloading operators. These are truly dangerous and can make "brittle" programs (when multiplying two
vectors does (*) signify a dot or cross product).
class Point {
num x;
num y;
num z;
Point.def() {
this.x=0;
this.y=0;
this.z=0;
} //end Point default set to zeros
Point.cartesian.mmm
Point.cartesian.mmmmmm
Point.cartesian.ftftft
You get the idea. Now forever I will have to write all this out. I'd much rather just write out Point(args) and know that my
overloaded constructors will take care of the rest because the prototype tells the compiler which constructor to use. Simple and
elegant, I repeat.
Any proposal for allowing operator type based overloading at least?
Now that Dart2.0 is statically typed can we have method/constructor overloading when using multiple types?
class Consumer<T> {
final List<T> models;
Consumer(T model, Function<T> builder): /**/;
Consumer(T model0, T model1, BiFunction<T, T> builder): /**/;
}
please reopen this topic.
Actually, I think you can just continue to push for it here, even though the topic is closed. The issue being closed doesn't stop anyone from counting how many arguments we have in either direction, etc.
That said, I'm among the folks who think that static overloading offers a bad trade-off: It may seem easier to write the code when all kinds of typing properties can be used to select one of many declarations, but it prevents first-class usage of the feature (which foo do you mean when you evaluate a method tear-off like x.foo?), it doesn't work if you want to make distinctions that do not involve type differences (new Point(double, double) could be a cartesian or a polar point), it makes code harder to read (in order to understand the meaning of parameters 1..5 at all, you may need to have very detailed knowledge about the _static type_ of parameter 6), it doesn't work for dynamic invocations (and we don't want crazy things like dynamic dispatch just for those invocations), it interacts in complex ways with inference (which is already a non-trivial topic ;-), etc.etc.
IMO method overloading is a super nice feature of a language - all languages would benefit from it, although I don't know if dynamic languages could implement it?
Dart 2 is no dynamic language anymore.
Follow #26488
Most helpful comment
Now that Dart2.0 is statically typed can we have method/constructor overloading when using multiple types?