-
Notifications
You must be signed in to change notification settings - Fork 205
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
Ability to provide constructor that forwards all arguments #493
Comments
And see also #418 which contains a broader discussion about how to abbreviate code that implements forwarding or "near-forwarding" invocations. |
A close relative to the abstract class Person {
factory Person({String name, DateTime dateOfBirth}) = Employee;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// |
// would like to not have to repeat all parameters
String get name;
DateTime get dateOfBirth;
}
class Employee implements Person {
Employee({this.name, this.dateOfBirth});
final String name;
final DateTime dateOfBirth;
}
main() {
Person(name: 'John', dateOfBirth: DateTime(1978, 12, 5));
} However, the /cc @mdebbar, who has been thinking about this same issue in the past |
A partial solution: similarly to @Hixie 's proposal let's use super arguments in child constructors but instead of the automatic …super, let developers use super arguments one by one as needed, the same way as this works. class MyPaddedButton extends TextButton {
final EdgeInsetsGeometry padding;
// current:
MyPaddedButton(Widget child, VoidCallback onPressed, this.padding, {VoidCallback? onLongPress})
: super(child: child, onPressed: onPressed, onLongPress: onLongPress);
//proposed:
MyPaddedButton(super.child, super.onPressed, this.padding, {super.onLongPress});
} It is just syntactic sugar and
but
Multi-level inheritance can be simplified to one level up as it works with the :super(y) calls: if the child exposes a parent argument in its constructor than that argument is offered to the grandchild as a super.y argument. |
That's cool! I can see how We would say that named parameters in corresponding class/superclass constructors correspond to each other if and only if they have the same name, but that's probably a good constraint because it's easier to read and understand a whole class hierarchy if the constructors use the same named parameters to mean the same thing. With that in place, we can handle a named parameter of the form At the end of the constructor there's a superconstructor invocation This is a feature that fits into the current language, and it would allow us to simplify cases like the following: class A {
int p;
A({this.p = 1});
}
// Today, we'd do this.
class B extends A {
int q;
B({int p = 1, this.q = 2}): super(p: p);
}
// Simplified class B, using `super.p`.
class B extends A {
int q;
B({super.p, this.q = 2});
} |
Especially when overriding a class with a lot of parameters, like some Flutter widgets, using For positional and non-named optional parameters, we can try to resolve the ambiguity with the following rules:
There can be a lint similar to class A {
int number;
String string;
A(this.number, this.string);
A.named({required this.number, required this.string});
}
class B extends A {
bool condition;
/// The ideal usage
B(super.number, super.string, this.condition);
// desugars to:
B(int number, String string, this.condition) : super(number, string);
/// Works, but because we can't rely on names, [string] is actually [number] and vice-versa.
///
/// This doesn't actually matter since we lose access to these arguments immediately after
/// passing them to `super`, and have to use them as regular fields after this.
B(super.string, super.number, this.condition);
// desugars to:
B(int string, String number, this.condition) : super(string, number);
/// Works, `this` params come before `super` params
B(this.condition, super.number, super.string);
// desugars to:
B(this.condition, int number, String string) : super(number, string);
/// Works, `this` params are in-between `super` params
B(super.number, this.condition, super.string);
// desugars to:
B(int number, this.condition, String string) : super(number, string);
/// Error, args declared optional, but `super` constructor is non-optional
B([super.number, super.string, this.condition]);
// desugars to:
B([int? number, String? string, this.condition]) : super(number, string); // error
/// Works, since all `super` args will have values
B([super.number = 0, super.string = "", this.condition = true]);
// desugars to:
B([int number = 0, String string = "", this.condition = true]) : super(number, string);
/// Works, `this` params come before `super` params
B(this.condition, [super.number = 0, super.string = ""]);
// desugars to:
B(this.condition, [int number = 0, String string = ""]) : super(number, string);
/// Error, since the `super` constructor doesn't provide names, and [number] and [string] are arbitrary.
///
/// It would be ambiguous as to which types [number] and [string] should have
B({required super.number, required super.string, required this.condition});
/// Works, since this "inherits" from [A.named].
B.named({required super.number, required super.string, required this.condition});
} Of course, if one requires advanced usage, such as omitting a parameter, they are free to use class A {
String string;
int number;
A.name1(this.string, this.number);
A.name2(this.number, this.string);
}
class B {
bool condition;
/// Which types are [string] and [number] bound to? It depends whether we use [A.name1] or [A.name2].
B(super.string, super.number, this.condition);
} I think in cases like these (specifically, no identically-named super constructor), Dart should force a manual EDIT:I revised this proposal by allowing non-super parameters to come before and in-between super parameters, see above. |
Having multiple super constructors is a common case so we should deal with it. B(super.string, super.number, this.condition): super.name1; |
@apps-transround wrote:
Definitely—I proposed that the class A {
String string;
int number;
A.name1(this.string, this.number);
A.name2(this.number, this.string);
}
class B extends A {
B(super.string, super.number, this.condition): super.name1();
} This makes it possible to connect If we stick to named parameters as @Levi-Lesches wrote:
I'm sure we could sort out the details. I have the impression that named parameters a really, really straightforward here, and positional parameters give rise to a number of ambiguities that we'd need to sort out carefully. The argument in favor of supporting positional parameters (including or excluding optional ones) is convenience and completeness, but the rules about how it actually works may be tricky to remember. The argument in favor of only supporting named parameters is simplicity and comprehensibility, but there may be added verbosity, especially for constructors with many positional arguments. Note that we might use optionally named parameters to allow many more constructors to use more named parameters without requiring callers to call them "namedly". We'll never get rid of that dichotomy. ;-) |
@eernstg @Hixie |
@apps-transround sorry, I missed that. Blog post has been updated! |
@mit-mit Thanks a lot! I truly appreciate your immediate response. |
Consider someone creating a Flutter package that extends a bunch of widgets with an extra argument, for example adding an
isEnabled
argument to every button, which changes how theonPressed
argument gets forwarded.Currently, if they do this, every time the superclass constructor is changed, they have to republish their package with an update. This leads to the likelihood that the package will eventually be abandoned and won't be updated (since that is work). It also means the package will only work with some versions (those that match the API the package expected).
Imagine if instead they could provide a constructor that is defined to have all the arguments of the superclass, with just a few "edits", as in:
...where
...super
stands for "fill in everything from the superclass that isn't overridden here".See also dart-lang/sdk#22274 which is similar but doesn't involve overriding. This proposal would make that one irrelevant (it just becomes
MySuperButton(...super)
or some such).The text was updated successfully, but these errors were encountered: