-
Notifications
You must be signed in to change notification settings - Fork 208
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
Using constructor and function parameter lists as implicit parameter groups #2234
Comments
It looks like the proposal is to make a formal parameter In other words, we'd be able to ask for "the rest" of the possible super-parameters using a single term, and if we want to customize something (say, the default value of a specific super-parameter) then we'd just go ahead and write it. That might certainly be a useful abbreviation feature on top of the new super-parameters feature! A couple of things should be considered: I'm guessing (based on the examples) that we'd only have Another thing to think about is maintainability: Suppose we'd have the "rest of supers" feature as an IDE feature rather than a language construct. We'd ask for "the rest of the super-parameters" in the IDE (using something like a quick fix), and then the appropriate source code would be generated. So we'd have all the super-parameters written out, same as today. Is it more maintainable as a language construct (where we'd have something like The underlying conflict is between two principles: (1) "Declare your interfaces explicitly", and (2) "Use abstraction to obtain more concise and consistent code". In the particular case where we're using abstraction to specify the interface, we may be hit by problems if said interface is too unstable, because it propagates changes made by others, elsewhere. Of course, super-parameters do that already, but this would mean that we do even more of it. |
The examples do not contain named constructors but the proposal strongly supports them. See Notation ... this/super . constructor/namedConstructor I think explicitly pointing to the constructor helps to avoid confusion. |
This proposal is rather about the language construct than the IDE feature. Named constructor support is available if super parameters implement :super.named or :super.named() as agreed here earlier. Regarding stability and change propagation: while creating this proposal
It’s something new but – I hope – it is similar enough to a proven and widely used methodology. I also hope that it really improves developer experience. |
This is something we did think about designing super-parameters, but decided to at least punt on. class Bar {
Bar.bar(T1 bar1, T2 bar2, {T3 barNamed});
}
class Foo extends Bar {
Foo.bar(int x, super) : super.bar();
} would be equivalent to Foo.bar(int x, super.bar1, super.bar2, {super.barNamed}) : super.bar(); aka Foo.bar(int x, T1 bar1, T2 bar2, {T3 barNamed}) : super.bar(bar1, bar2, barNamed: barNamed); One of the problems with that design is that you insert "all of the remaining" of the positional parameters at the What if you have;
Will that recognize the later What if some of the super-parameters are optional positional, is that inherited? What if it can't be? Baz(int x, super, int y); If the Even if So, if anything, the "rest" All in all, inheriting optional positional parameters implicitly is a big mess. |
As usual ;-), we could consider a less powerful (and arguably less confusing) mechanism where class Bar {
Bar(T1 bar1, {T2 bar2 = cBar2, T3? bar3, required T4 bar4});
}
class Foo extends Bar {
Foo(super.bar1, super); // Like Foo(super.bar1, {super.bar2, super.bar3, required super.bar4}).
Foo.name1(super.bar1, {required super.bar4, super}): super(bar3: null); // `super` --> `super.bar2`.
} This means that |
In my original proposal for super parameters, I wanted to have positional arguments but @eernstg 's comment convinced to eliminate them. The same happens now, I agree to have named argument only. Regarding the notation: …super is more self-explanatory for me than super Although the biggest win is simplifying super calls, please note that the current proposal also includes this.constructors and there is a related but more generic proposal here. |
Ignoring positional arguments is always a hard sell to me, because I very rarely use named parameters. They're just too cumbersome to be worth it in almost all cases. I don't think I've ever written a required named parameter outside of language tests. |
wouldn't it work if used as last argument and when there isn't an optional positional param? We hardly use/see constructors with optional params. In fact, we don't use optional positional params at all. |
It would be good to collect data on this (@munificent has done this kind of thing before). Anecdotally, widgets in Flutter make heavy heavy use of named parameters, FWIW. |
It seems like there are two options:
Keeping positional parameters may be done by understanding the differences between named and positional parameters and apply the learning on this feature.
So let's focus on positional parameters only. How this feature should work on them:
It's obvious that positional arguments cannot be identified by name, only by position: a parameter in another position is another parameter. So @lrhn example should result in a 'too many positional arguments' compile time error class A {
A(int a, int b, int c);
}
class B extends A {
B(super, super.a);
} For optional positional parameters it is said they can only be declared after any required parameters. So if super had any optional positional this should also result in error: Baz(int x, super, int y); Lint rules for these cases may really improve developer experience. Summary: it seems like the existing language syntax is enough to guide us in this case, too. Although careful usage is required for positional arguments we don't have to exclude them by default. |
Perhaps we could specify that A variant of this proposal would be to allow one or the other placement (not both), but a |
The theoretic question is that how the record spread operator should work inside another record. But Dart does not have Records at the moment and record spread is not even mentioned in the design docs. By checking other languages such as JavaScript and TypeScript, spread on objects is similar to spread on named arguments and spread on arrays is similar to spread on positional parameters and the behaviors (add, modify items) are in line with my previous comment. The problem is that although JS and TS support spread on both objects and arrays; but distinctly, not in the same operation. If we plan to support positional and named parameters at the same time, we have to define the behavior. What I proposed and still find valid:
|
Dart macros may offer a straightforward solution for this proposal without further language support. https://pub.dev/packages/parameters parameters macroExperimental implementation of the Dart Parameter Group proposal Using constructor and function parameter lists as implicit parameter groups #2234 (https://github.com/dart-lang/language/issues/2234/) via Dart static meta programming (macro). Please note:
GoalIn Object Oriented languages, classes and functional parts have good tooling (extends, implements, override, super, mustCallSuper, to name a few). This macro changes the way we handle parameters: All constructor, method and function definitions are implicit parameter groups.
Example 1: the Flutter FloatingActionButtonIn the floating_action_button.dart file the actual class has total 558 lines, including 201 comment line, so 357 real lines.
Hard to follow, easy to miss. const FloatingActionButton({
super.key,
this.child,
this.tooltip,
this.foregroundColor,
this.backgroundColor,
this.focusColor,
this.hoverColor,
this.splashColor,
this.heroTag = const _DefaultHeroTag(),
this.elevation,
this.focusElevation,
this.hoverElevation,
this.highlightElevation,
this.disabledElevation,
required this.onPressed,
this.mouseCursor,
this.mini = false,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.materialTapTargetSize,
this.isExtended = false,
this.enableFeedback,
}) : //asserts removed for clarity.
_floatingActionButtonType = mini ? _FloatingActionButtonType.small : _FloatingActionButtonType.regular,
_extendedLabel = null,
extendedIconLabelSpacing = null,
extendedPadding = null,
extendedTextStyle = null;
const FloatingActionButton.small({
super.key,
this.child,
this.tooltip,
this.foregroundColor,
this.backgroundColor,
this.focusColor,
this.hoverColor,
this.splashColor,
this.heroTag = const _DefaultHeroTag(),
this.elevation,
this.focusElevation,
this.hoverElevation,
this.highlightElevation,
this.disabledElevation,
required this.onPressed,
this.mouseCursor,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.materialTapTargetSize,
this.enableFeedback,
}) : // asserts removed
_floatingActionButtonType = _FloatingActionButtonType.small,
mini = true,
isExtended = false,
_extendedLabel = null,
extendedIconLabelSpacing = null,
extendedPadding = null,
extendedTextStyle = null;
// two more similar... With this macro package we can rewrite the constructors: instead of repeating the whole parameter list, we ask the macro to add all parameters of the FloatingActionButton constructor. @ParamFrom('FloatingActionButton.')
OptimizedFloatingActionButton.small({
this.autofocus = false,
this.clipBehavior = Clip.none,
}) The @ParamFrom('FloatingActionButton.') copies all the parameters from the default constructor and overrides the two parameters with the default values*. We haven't lost anything. The augmented code has all the parameters: // Generated code
augment class OptimizedFloatingActionButton {
augment OptimizedFloatingActionButton.small({
prefix0.Key? key,
prefix1.Widget? child,
prefix2.String? tooltip,
prefix3.Color? foregroundColor,
prefix3.Color? backgroundColor,
prefix3.Color? focusColor,
prefix3.Color? hoverColor,
prefix3.Color? splashColor,
prefix2.Object? heroTag,
prefix2.double? elevation,
prefix2.double? focusElevation,
prefix2.double? hoverElevation,
prefix2.double? highlightElevation,
prefix2.double? disabledElevation,
required void Function()? onPressed,
prefix4.MouseCursor? mouseCursor,
prefix2.bool mini,
prefix5.ShapeBorder? shape,
prefix3.Clip clipBehavior,
prefix6.FocusNode? focusNode,
prefix2.bool autofocus,
prefix7.MaterialTapTargetSize? materialTapTargetSize,
prefix2.bool isExtended,
prefix2.bool? enableFeedback,});
}
With the parameters macro we can spare 3*21 = 63 lines => 17% of the code. The resulting code is not only smaller but easier to understand because it clearly indicates what is the same and where are the differences. Example 2: Flutter TextFormFieldIt unites two worlds: FormField and TextField and you can see this on its constructor TextFormField({
super.key,
this.controller,
String? initialValue,
FocusNode? focusNode,
InputDecoration? decoration = const InputDecoration(),
TextInputType? keyboardType,
TextCapitalization textCapitalization = TextCapitalization.none,
TextInputAction? textInputAction,
TextStyle? style,
StrutStyle? strutStyle,
TextDirection? textDirection,
TextAlign textAlign = TextAlign.start,
TextAlignVertical? textAlignVertical,
bool autofocus = false,
bool readOnly = false,
@Deprecated(
'Use `contextMenuBuilder` instead. '
'This feature was deprecated after v3.3.0-0.5.pre.',
)
ToolbarOptions? toolbarOptions,
bool? showCursor,
String obscuringCharacter = '•',
bool obscureText = false,
bool autocorrect = true,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
bool enableSuggestions = true,
MaxLengthEnforcement? maxLengthEnforcement,
int? maxLines = 1,
int? minLines,
bool expands = false,
int? maxLength,
this.onChanged,
GestureTapCallback? onTap,
bool onTapAlwaysCalled = false,
TapRegionCallback? onTapOutside,
VoidCallback? onEditingComplete,
ValueChanged<String>? onFieldSubmitted,
super.onSaved,
super.validator,
List<TextInputFormatter>? inputFormatters,
bool? enabled,
bool? ignorePointers,
double cursorWidth = 2.0,
double? cursorHeight,
Radius? cursorRadius,
Color? cursorColor,
Color? cursorErrorColor,
Brightness? keyboardAppearance,
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
bool? enableInteractiveSelection,
TextSelectionControls? selectionControls,
InputCounterWidgetBuilder? buildCounter,
ScrollPhysics? scrollPhysics,
Iterable<String>? autofillHints,
AutovalidateMode? autovalidateMode,
ScrollController? scrollController,
super.restorationId,
bool enableIMEPersonalizedLearning = true,
MouseCursor? mouseCursor,
EditableTextContextMenuBuilder? contextMenuBuilder = _defaultContextMenuBuilder,
SpellCheckConfiguration? spellCheckConfiguration,
TextMagnifierConfiguration? magnifierConfiguration,
UndoHistoryController? undoController,
AppPrivateCommandCallback? onAppPrivateCommand,
bool? cursorOpacityAnimates,
ui.BoxHeightStyle selectionHeightStyle = ui.BoxHeightStyle.tight,
ui.BoxWidthStyle selectionWidthStyle = ui.BoxWidthStyle.tight,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ContentInsertionConfiguration? contentInsertionConfiguration,
MaterialStatesController? statesController,
Clip clipBehavior = Clip.hardEdge,
bool scribbleEnabled = true,
bool canRequestFocus = true,
}) This macro can simplify the code to something like this: @ParamFrom('TextField.', library: ''text_field.dart'')
@ParamFrom('FormField.', library: 'package:flutter/widgets.dart')
TextFormField()
The macro will put a doc comment link into the destination comments, pointing to the source parameter list doc comments, if any. MotivationThe motivation with this package is to
Issues
Due to these issues, the code is less attractive and not usable, but still very promising. How to proceedIf the Macros will be a part of Dart, and the missing features will be implemented, than this macro will really make an impact on how we work in Dart/Flutter. A decision is needed whether this remains a third-party package or will be somehow integrated into the core set. |
Creating constructors may require a lot of repeated code when using super, named and redirecting constructors as described here.
Although super parameters made a big impact, there is still room for improvement.
The proposed change is
Example:
Current code:
Code with the upcoming super parameters:
Code with the proposed feature:
Code with the proposed feature on this constructor:
Advantages
Explicit and implicit parameter conflicts are resolved with the following priority policy:
Notation: adding a keyword or a separator may improve clarity. Samples:
I agree with @eernstg that positional arguments can be very tricky, further evaluation is needed whether to support them.
A similar feature is to use the parameter list of a function as a parameter group with the new notation. For example:
Invocation is not changing:
The text was updated successfully, but these errors were encountered: