Skip to content

Parameter Groups #2245

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

Open
apps-transround opened this issue May 16, 2022 · 6 comments
Open

Parameter Groups #2245

apps-transround opened this issue May 16, 2022 · 6 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@apps-transround
Copy link

Problem

Handling long parameter lists either at declaration or at call/instantiation may require a lot of repeating and hard to read code. Flutter widgets are good examples where the actual parameters often just replace the default values for each instantiation:

return Column(
  mainAxisSize: MainAxisSize.min,
  crossAxisAlignment: CrossAxisAlignment.start,
  mainAxisAlignment: MainAxisAlignment.start,
  children: []
)
// or 
return Row(
  mainAxisSize: MainAxisSize.min,
  crossAxisAlignment: CrossAxisAlignment.start,
  mainAxisAlignment: MainAxisAlignment.start,
  children: []
)

Simplifying this can be done by
• adding specific named constructors with proper defaults, but that's an endless effort
• creating subclasses just to override defaults, but it seems to be an overkill resulting in non-shallow inheritance chains.

Proposal

As a new solution this proposal aims to create a new language feature called Parameter Group to make parameter lists shorter, easier to read and more reusable. For example, minStart is a Parameter Group:

const minStart = Flex.params(
    mainAxisSize: MainAxisSize.min,
    crossAxisAlignment: CrossAxisAlignment.start,
    mainAxisAlignment: MainAxisAlignment.start);

return Column(
  ...minStart,
  children: []
)
// or
return Row(
  ...minStart,
  children: []
)

A Parameter Group is a list of parameters including type, name and (default) value.
Parameter Groups are used inside parameter lists via the … (spread) operator, which expands all members of the group into the list.
Expansion is done virtually for the IDE wizards, autofill and documentation.
Parameter Groups can be

  • Implicit: each constructor, method and function parameter list creates an implicit parameter group that can be accessed via the name.params expression.
  • Explicit: variables defined as parameter groups like minStart in the example above. Parameter Groups of a certain type are created with the type.params(parameterList) expression.

Parameter groups can be placed into

  • instantiation / call: members of the group become the actual parameters if have value, such as in the example above
  • Declaration: see Using constructor and function parameter lists as implicit parameter groups for more details
    Example: because all Column constructor parameters are just passed to its superclass Flex we can simplify the Column() declaration using the implicit parameter group of the Flex constructor:
class Column extends Flex {
  Column({…Flex.params}) {
  }
}

Advantages

General:

  • Shorter, more reusable and more readable code
  • Transparency: changes in parameters can be transparent via Parameter Groups
  • Parameter Groups create brand new type of coding experience and may catalyze a whole wave of new features.
  • Independent from the existing solutions; does not increase structural complexity; easy introduction.

In declarations:

  • No change on the caller side
  • Only the specific parts are needed in the constructors.
  • Easy to see and understand overrides/differences.
  • Transparent changes, no need to modify all passing constructors

In calls:

  • Reusable, self-explaining code
  • May decrease Flutter widget hell pains: although the structure remains the same, less lines are used
  • Error proof predefined parameter groups for certain use cases

Considerations

  • Conflict resolution: Members of a parameter group may be in conflict with already defined parameters in the list. Although this can be treated as an error, probably a clear resolution priority chain is more developer friendly. Conflict between two parameter groups may still result in compile time error.
  • Value or default value: parameters in the declaration can have default values (int parameter1 = 6) and values in the calls (parameter1: 6). To have a reusable solution these two notations may mean the same for groups.
  • Declaration vs runtime evaluation scopes require further study.
  • Additional parameter group operations: Parameter groups are similar to Dart lists and this proposal uses the spread (…) operator. Other list methods and further operators (for example collection for, collection if) may be implemented if strong use cases are found.

Related topics

  • Parameter groups have the common goal with mixins to improve code reuse but target only parameter lists.
  • Dependency injection targets parameter passing but with a different goal.
  • Widget extensions roll up children with parameters to reduce tree depth
  • Super parameters
@eernstg
Copy link
Member

eernstg commented May 16, 2022

Interesting! I think, though, that this might already be covered by declaring the parameter group as a record, and using a spread operator. In that sense it could be considered as a vote in favor of having that spread operator.

Here's how that would look:

// A constant record, using named fields corresponding to certain named parameters.
const minStart = (
  mainAxisSize: MainAxisSize.min,
  crossAxisAlignment: CrossAxisAlignment.start,
  mainAxisAlignment: MainAxisAlignment.start,
);

// Example invocations where that record is 'spread' out, thus providing some actual arguments.
Column(...minStart, children: []);
Row(...minStart, children: []);

@mateusfccp
Copy link
Contributor

mateusfccp commented May 16, 2022

For some simpler cases, we can tear-off the constructors:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final widget = condition ? Column.new : Row.new;
    return widget(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      mainAxisAlignment: MainAxisAlignment.start,
      children: const [
        Text('Item 1'),
        Text('Item 2'),
        Text('Item 3'),
      ],
    );
  }
}

But it would be way nicer to have parameters-records equivalence.

@apps-transround
Copy link
Author

That’s good, I wasn’t aware of it.
Records are a perfect way of implementation for Parameter Groups but I don’t see they are already covering the features. What I’m missing from Records:

  • The development and compile time behavior of Parameter Groups. What “// Example invocations where that record is 'spread' out, thus providing some actual arguments.” means? How and when does it work?
  • Conflict resolution
  • Implicit Parameter Groups
  • Strongly typed parameter groups

@eernstg
Copy link
Member

eernstg commented May 16, 2022

The record proposal does not mention the spread operator (which would allow records to be passed in a way that desugars to several separate actual arguments), but it has been mentioned in a lot of discussions. It would be good if they were specified more precisely, but the main idea is as follows:

A spread operator on a record r, written as ...r, can be passed as an actual argument in a function invocations (which covers methods and all). The effect is that each of the components is passed as a separate actual argument, with positional components passed as positional arguments, and named components passed as named arguments.

This would (presumably) be a purely static mechanism, which means that it would only work when the static type of the record is the given record type (not dynamic, not Object, etc), and the parameters would be passed based on the static type (so if we have width subtyping for records then the ones that aren't known at compile time would not be passed).

@apps-transround
Copy link
Author

Now I see. Thanks! Of course, count this as a vote on the record spread operator.
I rewrite my proposal with records.

@apps-transround
Copy link
Author

Using records in formal and actual parameter lists

Applies to all elements with parameter list: constructors, methods, functions, extensions…
Names are temporary, please advise.

Getters

//Returns the parameters as a record type
Element.paramsType

//Returns the formal parameters as a record expression
Element.paramsType record = element.paramsFormal; 

//Returns the actual parameters as a record expression
Element.paramsType record = element.paramsActual; 

Spread (…)

The … operator expands the record fields into the containing list:

  • Declarations: record fields are added to the declaration of the formal parameters
  • Call: record fields are added as actual parameters.

If the expanded fields overlap with list items conflict resolution is a question:

  • Throw an error
  • Apply order of execution and let the latest win

Constructor

It would be great if IDE wizards and autofill could support typed records. Although the record specification excludes the naming of record types, something similar can work:

var flexParamRecord = Flex.paramsType(    
   mainAxisAlignment: MainAxisAlignment.start,
   mainAxisSize: MainAxisSize.min,
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

3 participants