-
Notifications
You must be signed in to change notification settings - Fork 214
add function overloading #1741
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
Comments
Duplicate of #1122. |
But Dart does have optional parameters, with default values. I don't use Go, but here's what I think you want: void wait(Conditioncondition, [Duration timeout = DefaultWaitTimeout, Duration interval = DefaultWaitInterval]) {
// implementation here
} |
Optional parameter is not the same thing as function overloading, I believe that code is cleaner with function overloading, as it avoids putting a bunch of optional parameter in addition to other issues, it is much more difficult to port java/c#/c++ code to dart without the function overload. I have a large C# and java code base that I'm porting to dart and it's very difficult to do this without function overloading. Optional parameters has to be last. So you can not add an extra parameter to that method unless its also optional. I believe they serve different purposes. Optional parameters are for when you can use a default value for a parameter, and the underlying code will be the same: public CreditScore CheckCredit( public void SendSurvey(IList customers, int surveyKey) { Optional parameters should be used if the parameters can have a default value. Method overloading should be used when the difference in signature goes beyond not defining parameters that could have default values (such as that the behavior differs depending on which parameters are passed, and which are left to the default). // this is a good candidate for optional parameters // this is not, because it should be one or the other, but not both // these are good candidates for overloading // these are no longer good candidates for overloading |
(Emphasis added) If you want to make two methods that have different behavior based on the types of the parameters passed to them, consider making differently-named methods? For example, the "do X with one object"/"do X with this list of objects" pair is often named public void SendSurvey(IList customers, int surveyKey) {
// will loop and call the other one
}
public void SendSurvey(Customer customer, int surveyKey) {
// ...
} void sendSurveyAll(List<Customer> customers, int surveyKey) {
for (final Customer customer in customers) sendSurvey(customer, surveyKey);
}
void sendSurvey(Customer customer, int surveyKey) { /* ... */ } Dart works on a philosophy that you can tell the meaning of a line of code simply by looking at the types involved. Method overloading makes this relationship ambiguous, as some values/types may be determined by logic. By using different method names, you make your intentions clear to the reader. |
That is true when you have proper names for functions, however, there are cases that method overloading makes sense and, from what we know, there is no other way to express in Dart or at least in a sound way. In our case we have a builder that generates different arguments for a method that the intention is the same: searching. Future search({String foo, int bar});
Future search({Date daz, bool baz}); So we don't have proper names for those methods, they are indeed the same but with different arguments. |
Future searchForString({String pattern, int count});
Future searchForDate({DateTime date, bool includeLeapYears}); Something like that. If you think that's repetitive, you should see:
Not saying this is the perfect naming system, just making the point that there already is a convention in place for these kinda things. |
Unfortunately this is not possible for us. That was just a very simple example.
All those functions have proper names. In our case, as stated above, we don't have those names. We thought of some options:
|
I think it's important to differentiate "merging multiple functions into one name" from "trying to make incompatible parameters of the same function compile-safe" I believe what many say here is, we don't want to support the former. But the latter is a good use-case. The goal being, we should be able to remove the asserts from the This widget allows: Positioned(top: x, bottom: x, left: x, right: x)
Positioned(top: x, bottom: x, left: x, width: x)
Positioned(top: x, left: x, width: x, height: x)
... but does not allow: Positioned(top: x, bottom: x, left: x, right: x, width: x)
Positioned(top: x, bottom: x, left: x, right: x, height: x)
... This is not a case where the function would usually be split into multiple smaller ones. Rather we want to express a union of the different parameters. This could be defining Positioned({required int top, required int bottom, required int left, required int right});
Positioned({required int top, required int bottom, required int left, required int width});
Positioned({required int top, required int left, required int width}); where the implementation would be a single function with the prototype: Positioned({int? top, int? bottom, int? left, int? width, int? height}); |
@rrousselGit from the developer view that is using the I understand that method overloading can be a pain to read the code and it makes autocomplete a mess. IMO we could a strict method overloading by not allowing core type arguments with same name, ie: void foo(int bottom);
void foo(String bottom); // do not allow this If the dev changes the name of the argument but uses a simple parse to call another method, disallow or lint over it:
I think if we avoid somehow the overuse of method overload, then it is a very good addition to the language. |
In C# autocomplete works perfectly with code fill with overloaded functions. @Levi-Lesches |
That is really dependent on the library you are consuming. For that reason I fully understand the Dart team and I think we could have a stricter kind of overloading. |
I'm not very much in favor of adding reserved words in the language, but I think to solve this situation, and have a more rigid (stricter) function overload, I could have a reserved word "over" for overloaded functions and maybe restrict it to only class methods class Vector4
{
Vector4(double x, double y, double z, double w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
over Vector4(Vector2 v)
{
x = v.x;
y = v. y;
z = 0.0f;
w = 0.0f;
}
over Vector4(Vector3 v)
{
x = v.x;
y = v.Y;
z = v.Z;
w = 0.0f;
}
static Vector4 transform(Vector4 vec, Matrix4X4 mat)
{
Vector4 result;
...
return result;
}
static over Vector4 transform(Vector4 vec, Quaternion quat)
{
Vector4 result;
...
return result;
}
} |
Why use |
I use a pattern that works in Rust, but would be great to have in Dart: abstract class Receiver<T> {
void handleMessage(T msg);
}
class ListPage extends StatefulWidget {
const ListPage({ Key? key }) : super(key: key);
@override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> implements Receiver<EntryMessage>, Receiver<UserEditMessage> {
@override
void handleMessage(EntryMessage msg) {
// handle entry message type
}
@override
void handleMessage(UserEditMessage msg) {
// handle user edit message
}
} Rust doesn't have function/method overloading but allows types to implement generic traits (interfaces) like this. I think that looks really neat |
@Diegovsky that looks like method overloading to me |
Kinda. I'm not sure if you have experience with Rust, but trait (interface) implementations are a bit like Dart's extensions. The compiler treats two trait methods from different traits which have the same name but different signatures as different things. I think (not a rust expert) the compiler sees a method (e.g I think it might not be too hard for dart to implement it because it knows what type a lambda argument is based on the method you're giving it to, e.g: ["foo", "bar"].forEach((e) => print(e))). // Can also be (String e) => print(e) @tatumizer Well, if you don't use var handler = pageState.handleMessage;
handler(EntryMessage()); // Using type inference Function(EntryMessage) handler = pageState.handleMessage; // Specifying the type |
Indeed, I'm not proposing function overloading like C++, java and C# have. I'm not knowleadgable enough on language design to say how that's different from normal function overloading but I've seen people saying that doing it with traits it's better than C++'s way. I would argue that in the specific case I showed the methods being overriden come from different sources so it could be possible since dart does not do type erasure. I focused on rust as it allows this but I don't think rust's implementation of it would be fit for dart either. Something like F#'s resolution might work better in dart? I'm not sure anymore tbh. This mechanism is more complex than I expected :/ |
@Diegovsky, I suppose your example would correspond to something like the following in Rust?: trait Receiver<T> {
fn handle_message(&self, msg: T);
}
struct EntryMessage;
struct UserEditMessage;
struct ListPageState;
impl Receiver<EntryMessage> for ListPageState {
fn handle_message(&self, _msg: EntryMessage) {
println!("handle entry message");
}
}
impl Receiver<UserEditMessage> for ListPageState {
fn handle_message(&self, _msg: UserEditMessage) {
println!("handle user edit message");
}
}
fn main() {
let x: ListPageState = ListPageState {};
x.handle_message(UserEditMessage {});
x.handle_message(EntryMessage {});
} As you mentioned here, Dart extension methods are quite similar to Rust The main difference is that Rust (apparently) uses the actual arguments' types to determine which implementation is appropriate for the given call, whereas Dart immediately reports an ambiguity error when it has been determined that there are multiple extension methods which are available and applicable, and none of them is more specific than all the others. In Dart, we can actually disambiguate the invocations: // ----------------------------------------------------------------------
// Empty declarations for missing parts.
class StatefulWidget {
const StatefulWidget({Key? key});
}
class Key {}
class State<X> {}
// Marker interface, used to guide extension method resolution.
abstract class Message implements EntryMessage, UserEditMessage {}
class EntryMessage {}
class UserEditMessage {}
// ----------------------------------------------------------------------
abstract class Receiver<T> {}
class ListPage extends StatefulWidget {
const ListPage({ Key? key }) : super(key: key);
_ListPageState createState() => _ListPageState('');
}
class _ListPageState extends State<ListPage> implements Receiver<Message> {
String id;
_ListPageState(this.id);
}
extension on Receiver<EntryMessage> {
void handleMessage(EntryMessage message) {
print('Handle entry message.');
}
}
extension on Receiver<UserEditMessage> {
void handleMessage(UserEditMessage message) {
print('Handle user edit message.');
}
}
void foo(Receiver<EntryMessage> x1, Receiver<UserEditMessage> x2) {
x1.handleMessage(EntryMessage());
x2.handleMessage(UserEditMessage());
}
void main() {
var x = _ListPageState('MyId');
foo(x, x);
// x.handleMessage(EntryMessage()); // Error, ambiguous.
} The invocations in Note that this mechanism is powerful enough to allow methods with the same signature to coexist, because it is the static type of the receiver that determines which implementation to run. So, for example, we can use this to have several different versions of a getter The downside is that it may be inconvenient to have to make this choice by ensuring that the receiver has a specific static type. For instance, at the end of We could do We could also re-shape the invocations such that the choice of method is determined by the object that actually matters here, that is, the argument: class ListPage {}
class Key {}
class State<X> {}
class EntryMessage {}
class UserEditMessage {}
class _ListPageState extends State<ListPage> {
String id;
_ListPageState(this.id);
}
extension on EntryMessage {
void passMessageTo(_ListPageState state) {
print('Handle entry message.');
}
}
extension on UserEditMessage {
void passMessageTo(_ListPageState state) {
print('Handle user edit message.');
}
}
void main() {
var x = _ListPageState('MyId');
EntryMessage().passMessageTo(x);
UserEditMessage().passMessageTo(x);
} You could say that this is a "reverse method invocation": The argument is provided as the syntactic receiver, and the receiver is provided as the syntactic argument. It would generalize to multiple arguments if we had tuples: This would allow for completely unrelated signatures for Of course, this is not really an option in real software design: If we use this approach then the extension method named We might be able to use Nevertheless, the whole "reverse method" idea is kind of fun. 😄 |
I agree that we can use the function signature to determine applicability. If two function types F1 and F2 taking named parameters (actually, any function types would do) are in a subtyping relationship (say, F2 <: F1) then every type correct invocation of a function of type F1 is also guaranteed to be a type correct invocation of a function of type F2. Conversely, if the two types are not the same type then there will be invocations of a function of type F2 that aren't correct for F1. So we can always use a type check on the invocation to at least determine which subset of the given statically overloaded variants would admit any given list of actual arguments, and then we can check whether that set of candidates has a most specific element. The fact that a declaration that doesn't accept a named parameter with the name So disambiguation isn't that much of a problem in itself. The problem is more likely to be that it is difficult to get a good understanding of which invocations would be ambiguous, for any given set of static overloads. If we require that every member of a given set of static overloadings must have a |
Yes, that's the example I was trying to give. I'm still not very good with words so I couldn't put it so well like you did. |
+1 for function overloading |
Having function overloading would resolve a lot of problems that now can only by solved using dynamic or separated interfaces. Using dynamic is pretty bad because it allow user to call the function with a type we don't want. Solutions liked freezed or other are not at all solutions since they produce an overlay over the data... The only one that makes me loose my mind figuring out workarounds that in fact do not exist. |
@Andrflor Could you give us a concrete example? The only case where I feel like overload really matters is for operators, because for regular methods we can simply give each of them a different (and more meaningful) name. As much as you may think it is a hassle, it is at most an "aesthetic" preference of using the same name for different function signatures vs using a different name for each signature. Thus, I don't see it as a limitation that requires workarounds, unless I am missing something important here. Also, isn't this issue a duplicate of #1122? |
Even a simple To understand how weird is not to have method overloading, imagine if we changed the
|
I can't say if As I said, you may find a hassle to not have method overload, but @Andrflor said that it makes it impossible to implement some constructs without using workarounds and |
No you can't.
If
|
Let's imagine two classes that have the same real interface but two different programmatic interfaces. For example from one package class Rx<T> {
Stream<T> stream;
} From a differnet package class Obs<T> {
Stream<T> stream;
} In real life they share the same interface. I would like to do bind(Obs obs) {
bindStream(obs.stream);
}
bind(Rx obs) {
bindStream(obs.stream);
} or bind(Obs or Rx obs ) {
obs is Obs? bindStream(obs.stream) : bindStream(obs.stream);
// First part is cast as Obs // This is cast as Rx
} I'm forced to do bind<T extends Object>(T obs) {
try {
bindStream((obs as dynamic).stream);
} on Exception catch (_) {
throw '$T has not method [stream]';
}
} Function overloading will be way better and much more simple. @mateusfccp I don't really see why bindObs and bindRx would make more sense since they have the common interface. |
@Andrflor you could write |
Considering you own those classes, you could model them this way: abstract class WithStream<T> {
Stream<T> get stream;
}
class Rx<T> with WithStream<T> {
@override
final Stream<T> stream;
}
class Obs<T> with WithStream<T> {
@override
final Stream<T> stream;
}
void bind<T>(WithStream<T> withStream) {
bindStream(withStream.stream);
} If you don't own this classes, you could model it with a trait-like, which Dart unfortunately doesn't have (see #1612), or, as @Wdestroier suggested, something like untagged unions (i.e. Regardless, this doesn't seem to be a problem to be solved by overload. |
@Andrflor, even if you don't own the class, you can still do this with a little trick called mixins: // The original code that you don't own
class Rx<T> { Stream<T> stream; Rx(this.stream); } // package1.dart
class Obs<T> { Stream<T> stream; Obs(this.stream); } // package2.dart
// Your code:
// Import the original classes under a prefix...
import "package1.dart" as p1;
import "package2.dart" as p2;
// ...redeclare them under a new interface, StreamGetter...
mixin StreamGetter<T> { Stream<T> get stream; }
class Rx<T> = p1.Rx<T> with StreamGetter<T>;
class Obs<T> = p2.Obs<T> with StreamGetter<T>;
// ... and write your code with StreamGetter
void bindStream<T>(Stream<T> stream) { }
void bind<T>(StreamGetter<T> obj) {
if (obj is Rx) { /* some complex work here */ }
else if (obj is Obs) { /* some complex work here */ }
bindStream(obj.stream);
}
// Your user's code:
Stream<int> getInts() async* {
for (final int value in [1, 2, 3]) { yield value; }
}
void main() {
StreamGetter rx = MyRx<int>(getInts());
bind(rx);
} This fundamental problem here isn't overloading, it's the fact that you don't control the original code. If these two objects are indeed related. then they should be declared to do so, but if you don't own the original code, you can't make that very simple change. This workaround may seem clunky on your side, but it simplifies the API for your users, who see Also, yes, this should probably be closed as a duplicate of #1122 |
@Levi-Lesches |
@Andrflor, I agree with several others that the discussion about your example here is mostly about member set management (we have subtyping, and we could have structural types or union/intersection types). Those discussions should be taken in an issue about structural typing, #1612, or about union types, #83 or #145. However, we do have a particular kind of static overloading with extension methods, and this mechanism can be used as follows (using the example from @Levi-Lesches as a starting point): // ----- Library 'lib.dart'.
// This is the original code that you don't own.
class Rx<X> {
Stream<X> stream;
Rx(this.stream);
}
class Obs<X> {
Stream<X> stream;
Obs(this.stream);
}
// ----- Library 'framework.dart'.
// Your code.
// import 'lib.dart';
void bindStream<T>(Stream<T> stream) {}
extension BindStreamRx<X> on Rx<X> {
void bind() {
/* some complex work here */
bindStream(stream);
}
}
extension BindStreamObs<X> on Obs<X> {
void bind() {
/* some complex work here */
bindStream(stream);
}
}
// ----- Library 'user.dart'.
// Your user's code.
// import 'framework.dart';
Stream<int> getInts() async* {
for (final int value in [1, 2, 3]) {
yield value;
}
}
void main() {
var rx = Rx<int>(getInts());
rx.bind();
var obs = Obs<int>(getInts());
obs.bind();
} This won't allow you to avoid the duplication of the code However, a major reason why Dart is using nominal subtyping is that the explicit subtype relation implies substitutability, and we only have a subtype relation when some developer has chosen to declare that subtype relationship, implying that a family of declarations with the same name will "do the same thing", and hence it's OK to invoke a member with the given name even though we have no knowledge (at compile time) about which one will actually be executed at run time. In the example above, there is no reason to assume that the purpose and behavior of This means that the two invocations are considered to be fundamentally different, and hence there is no way to abstract over the difference, and combine those two invocations at the same call site, .. Except, of course, if we use a receiver of type There is very little difference between the approach shown above, and using separate bind functions: void bindRx<X>(Rx<X> rx) {
/* some complex work here */
bindStream(rx.stream);
}
void bindObs<X>(Obs<X> obs) {
/* some complex work here */
bindStream(obs.stream);
} Finally, if you do not want to invoke the two different getters named |
@mateusfccp if I owned the classes the problem would not exists.. @eernstg Well that's my bad class Rx<T> {
void bind(Obs<T> obs) => bindStream(obs.stream);
void bind(Rx<T> obs) => bindStream(obs.stream);
void bindStream(Stream stream) {
// Complex Work here
}
} For the user binding with Rx or binding with Obs does not make any difference... This is also not working as extension extension BindRx<T> on Rx<T> {
void bind(Rx<T> obs) => bindStream(obs.stream);
}
extension BindObs<T> on Rx<T> {
void bind(Obs<T> obs) => bindStream(obs.stream);
} This will only allow the first one. You're right this is more about structural types or union/intersection types. |
I'm not sure what
You don't have static overloading on parameter types using extensions (or using anything in Dart), but you do have a kind of static overloading on the receiver type using extensions. So you could do this (which is what I mentioned in an earlier example): extension BindRx<X> on Rx<X> {
void bind() => bindStream(this.stream);
}
extension BindObs<X> on Obs<X> {
void bind() => bindStream(this.stream);
} You would have to do Also, we'll have records soon (for some value of 'soon' ;-), so if you want to do something similar for several types you can just dispatch on a record: extension HandleIntInt on (int, int) {
void handle() {/*complex stuff on int and int*/}
}
extension HandleIntDouble on (int, double) {
void handle() {/*complex stuff on int and double*/}
}
// other combinations that need their own implementations.
void main() {
(1, 2).handle(); // Runs the code for `int` and `int`.
(1, 2.5).handle(); // Runs the code for `int` and `double`.
} This is similar to a static overloading mechanism with cases |
I meant I don't try to bind Rx or Obs to itself. @mateusfccp in a sense I agree that the given example is more about aesthetic but in a sense not really. extension RxIntExt on Rx<int> {
bool operator <(int other) => value < other;
bool operator <(Rx<int> other) => value < other.value;
} This won't work because it has the same name. Am I wrong or there is no operator overloading in dart, but just operator overriding? Because even if I just declare extension RxIntExt on Rx<int> {
bool operator <(int other) => value < other;
} I've lost the symmetrical property because i can write extension Operator on int {
bool operator <(Rx<int> other) => this < other.value;
} But it's complete dead code since it won't do anything. Btw, like already mentioned it seems to be a duplicate of #1122 |
OK, I should have made the "use extensions to get static overloading on the receiver" example more complete. Here we go: // ----- Glue code.
class Obs<X> {
final Stream<X> stream;
Obs(this.stream);
}
class Both implements Rx<Never>, Obs<Never> {
@override
noSuchMethod(Invocation i) => throw 0;
}
Both get any => Both(); // Just so we can write `any` where we mean `/* some expression here */`.
// ----- First version:
// Pretend that we have static overloading on method parameters.
class Rx<X> {
final Stream<X> stream;
Rx(this.stream);
void bind(Obs<X> obs) => bindStream(obs.stream);
void bind(Rx<X> rx) => bindStream(rx.stream);
void bindStream(Stream stream) {
// Complex Work here
}
}
void useIt() {
Rx<int> rx = any, rx2 = any;
Obs<int> obs = any;
rx.bind(rx2); rx2.bind(obs); // OK.
}
// ----- Second version:
// Just use different names.
class Rx<X> {
final Stream<X> stream;
Rx(this.stream);
void bindObs(Obs<X> obs) => bindStream(obs.stream);
void bindRx(Rx<X> rx) => bindStream(rx.stream);
void bindStream(Stream stream) {
// Complex Work here
}
}
void useIt() {
Rx<int> rx = any, rx2 = any;
Obs<int> obs = any;
rx.bindRx(rx2); rx2.bindObs(obs); // OK.
}
// ----- Third version:
// Use static overloading on receiver.
extension GetBoundByRx<X> on Rx<X> {
void getBoundBy(Rx<X> rx) => rx.bindStream(stream);
}
extension GetBoundByObs<X> on Obs<X> {
void getBoundBy(Rx<X> rx) => rx.bindStream(stream);
}
class Rx<X> {
final Stream<X> stream;
Rx(this.stream);
void bindStream(Stream stream) {
// Complex Work here
}
}
void useIt() {
Rx<int> rx = any, rx2 = any;
Obs<int> obs = any;
rx2.getBoundBy(rx); obs.getBoundBy(rx2); // OK.
} |
Closing as a duplicate of #1122 |
For what it's worth, overloading is a problem that's in NP-complete and can cause slow build times. Probably not worth it imo if you want fast builds in any language. https://web.cs.ucla.edu/~palsberg/paper/dedicated-to-kozen12.pdf |
you can also duplicate parameters (because it's funny, as well....) so |
That only applies to a very specific variant where type inference and overloading are interconnected (the proof is given in context of λ-calculus with overloading). This does not apply to most mainstream programming languages which usually require you to specify signatures of methods rather then inferring these signatures. Indeed the paper covers this in Section 8 where it explains that overload resolution in Java is not NP-complete. |
I remember a performance issue in C# related to type inference and overloading. I couldn't find the resolved issue in YouTrack, but I found another one which looks like the same problem. https://youtrack.jetbrains.com/issue/RIDER-77533/Rider-freezing-when-analyzing-nested-LINQ-queries. I still think it's a good feature for a few cases. @esoros isn't it better to continue the discussion to support function and method overloads in the open issue? |
That's basically the same problem....but sure that's fine. The output parameter is a formal parameter too, so when people have languages where you can write "var x = "asdasdasd" and overloads you get slow builds, because not all of the formal parameters are present. In the enterprise or for large projects people like types such as IDomainLoader<Dictionary<IMappedUserLoader<CustomerDomainStrategy, CusomerUserLoaderStrategy>>>> and then don't want to type that in repeatedly. If languages don't support any type inference, then overloading is fine.....that' s a tradeoff. |
First of all, what is function overloading? Function overloading is a feature of a programming language that allows one to have many functions with same name but with different signatures.
This feature is present in most of the Object Oriented Languages such as C++, Java, C# ...
Function overloading is a feature of object oriented programming where two or more functions can have the same name but different parameters.
When a function name is overloaded with different jobs it is called Function Overloading.
In Function Overloading “Function” name should be the same and the arguments should be different.
Function overloading can be considered as an example of polymorphism feature in C++.
Following is a simple C++ example to demonstrate function overloading.
I’ll post a real code example here. When I was working on Golang’s Selenium binding, I needed to write a function that has three parameters. Two of them were optional. Here is what it looks like after the implementation:
I had to implement three different functions because I couldn’t just overload the function—Go doesn’t provide it by design.
I have to admit that sometimes function overloading can result in messy code. On the other hand, because of it, programmers need to write more code.
Dart currently has the same limitation as Go, that is, no function overload, I believe that function overload speeds up and makes software development much easier, I believe it's more in favor than against.
The text was updated successfully, but these errors were encountered: