New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for method overloading when using typed arguments #49

Closed
DartBot opened this Issue Oct 11, 2011 · 17 comments

Comments

Projects
None yet
10 participants
@DartBot
Copy link

DartBot commented Oct 11, 2011

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:

  1. Return types for overloaded methods would have to match. This is no different from other languages with overloading support, so nothing new here.
  2. All types would have to be declared on each variant when declaring an overloaded method. The compiler could easily check for this. I don't think users would be surprised by such an error if they ran into it-- seems intuitive enough.

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?

@dgrove

This comment has been minimized.

Copy link
Member

dgrove commented Oct 11, 2011

Removed Type-Defect label.
Added Type-Enhancement, Area-Language labels.

@DartBot

This comment has been minimized.

Copy link
Author

DartBot commented Oct 11, 2011

This comment was originally written by drfibonacci@google.com


Added Triaged label.

@DartBot

This comment has been minimized.

Copy link
Author

DartBot commented Oct 11, 2011

This comment was originally written by jat@google.com


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.

@DartBot

This comment has been minimized.

Copy link
Author

DartBot commented Oct 11, 2011

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).

@DartBot

This comment has been minimized.

Copy link
Author

DartBot commented Oct 11, 2011

This comment was originally written by jat@google.com


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?

@DartBot

This comment has been minimized.

Copy link
Author

DartBot commented Oct 11, 2011

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.

@DartBot

This comment has been minimized.

Copy link
Author

DartBot commented Oct 12, 2011

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.

@gbracha

This comment has been minimized.

Copy link
Contributor

gbracha commented Oct 12, 2011

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.

@DartBot

This comment has been minimized.

Copy link
Author

DartBot commented Oct 12, 2011

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.

@DartBot

This comment has been minimized.

Copy link
Author

DartBot commented Nov 16, 2013

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.

@DartBot

This comment has been minimized.

Copy link
Author

DartBot commented Jun 20, 2014

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=r
sin(theta); //set y
    this.z=z; //set z
  } //end Point for cylindrical coordinates

  Point(num: p, num: theta, num: phi) { //spherical coordinates
    this.x=p*sin(phi)cos(theta); //set x
    this.y=p
sin(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=r
sin(theta); //set y
    this.z=z; //set z
  } //end Point for cylindrical coordinates

  Point(double: p, num: theta, num: phi) { //spherical coordinates
    this.x=p*sin(phi)cos(theta); //set x
    this.y=p
sin(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=r
sin(theta); //set y
    this.z=z; //set z
  } //end Point for cylindrical coordinates

  Point(num: p, double: theta, num: phi) { //spherical coordinates
    this.x=p*sin(phi)cos(theta); //set x
    this.y=p
sin(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=r
sin(theta); //set y
    this.z=z; //set z
  } //end Point for cylindrical coordinates

  Point(num: p, num: theta, double: phi) { //spherical coordinates
    this.x=p*sin(phi)cos(theta); //set x
    this.y=p
sin(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=r
sin(theta); //set y
    this.z=z; //set z
  } //end Point for cylindrical coordinates

  Point(double: p, double: theta, num: phi) { //spherical coordinates
    this.x=p*sin(phi)cos(theta); //set x
    this.y=p
sin(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=r
sin(theta); //set y
    this.z=z; //set z
  } //end Point for cylindrical coordinates

  Point(double: p, num: theta, double: phi) { //spherical coordinates
    this.x=p*sin(phi)cos(theta); //set x
    this.y=p
sin(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=r
sin(theta); //set y
    this.z=z; //set z
  } //end Point for cylindrical coordinates

  Point(double: p, double: theta, double: phi) { //spherical coordinates
    this.x=p*sin(phi)cos(theta); //set x
    this.y=p
sin(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=r
sin(theta); //set y
    this.z=z; //set z
  } //end Point for cylindrical coordinates

  Point(num: p, double: theta, double: phi) { //spherical coordinates
    this.x=p*sin(phi)cos(theta); //set x
    this.y=p
sin(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.

@ronlobo

This comment has been minimized.

Copy link

ronlobo commented Sep 5, 2016

Any proposal for allowing operator type based overloading at least?

@passsy

This comment has been minimized.

Copy link

passsy commented Mar 30, 2018

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): /**/;
}
@escamoteur

This comment has been minimized.

Copy link

escamoteur commented Apr 4, 2018

please reopen this topic.

@eernstg

This comment has been minimized.

Copy link
Member

eernstg commented Apr 4, 2018

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.

@ORESoftware

This comment has been minimized.

Copy link

ORESoftware commented Jan 31, 2019

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?

@zoechi

This comment has been minimized.

Copy link
Contributor

zoechi commented Jan 31, 2019

Dart 2 is no dynamic language anymore.
Follow #26488

This issue was closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment