-
Notifications
You must be signed in to change notification settings - Fork 207
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
Proposal for a pipe-like operator to chain constructor/method invocations without nesting #4211
Comments
On first glance I like this proposal because conceptually it seems similar (for me) to the way that |
See also #1246 (Feb 17, 2014) |
I like the idea, but I think we should consider using a different operator. Since I’m using a Turkish layout keyboard, pressing four keys to type the |> operator is a bit inconvenient. It might also be the case for other keyboard layouts. Perhaps we could use something like this instead:
|
Pipe is a bit pain to type. Colon?
|
Yes, I also think that it is very important to maintain the order of widgets. And we also need some simplifications. And I like the proposed approach. It will allow me to choose how I want to write the code without losing the overall logic and structure of the widgets. What I mean: For example: Example 1:
Container(width: 200, height: 200, alignment: .center, color: .green)
|> DefaultTextStyle(style: TextStyle(color: .black))
|> Text('Bloc', textAlign: .center, textScaler: TextScaler.linear(2));
Container(
width: 200,
height: 200,
color: .green)
|> DefaultTextStyle(
style: TextStyle(color: Colors.black),
)
|> Text(
'Hello World',
textAlign: TextAlign.center,
); Some additional thoughts: t may also be interesting to extend the logic of working with the Example 2:
Container(width: 200, height: 200, color: Colors.grey)
|> with (
color: isActive ? Colors.green : Colors.red, // logic for parameter color
alignment: Alignment.center,
child: (child) => Padding(
padding: EdgeInsets.all(isActive ? 16 : 8),
child: child,
),
)
|> Text(label, style: TextStyle(color: Colors.white));
Center(
|> ElevatedButton(style: ButtonStyle(padding: EdgeInsets.all(5.0)),)
|> with (
onPressed: () {}, // separated callback
child: (child) => Text("Tap Me!"),
); This would allow us to have a separate description of UI components from handlers, which looks quite convenient and clean. It looks like a kind of paired extension class or functional mutator. As I understand it, this will work too? Example 3:Column(mainAxisAlignment: MainAxisAlignment.center)
|> [
Text('Item 1'),
Text('Item 2'),
Text('Item 3'),
]
|> Padding(padding: EdgeInsets.all(8)); It would also be interesting to have branching in a pipable container, for example, something like this: Example 4:Container(width: 200, height: 200)
|> (isActive ? Text('Active') : Text('Inactive')); Although, in theory, it could be part of the general “with” extension: Container(width: 200, height: 200)
|> with (child: (_) => isActive ? Text('Active') : Text('Inactive')); These are just a few ideas for discussing how to simplify and improve UI and code composition. And my impressions of the proposed approach |
I REALLY like this proposal!!! (Sorry for the !!! but I'm really excited)
If we could combine that with automatic currying of the last parameter of function calls that get used with the piping operator we wouldn't even need a new
|
I really like this as it
Example with a bit more nested structure: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Padding(
padding: EdgeInsets.all(5),
child: ColoredBox(
color: Colors.red,
child: SizedBox(
width: 50,
child: Text('Item 1'),
)
),
)
),
Text('Item 2'),
Text('Item 3'),
],
) vs Column(mainAxisAlignment: MainAxisAlignment.center)
|> [
Center()
|> Padding(padding: EdgeInsets.all(5))
|> ColoredBox(color: Colors.red)
|> SizedBox(width: 50),
|> Text('Item 1'),
Text('Item 2'),
Text('Item 3'),
] and just an idea, while it is not really important now, about syntax, maybe formater might not even put space in front of |> since its already big enough and IMO still readable? Column(mainAxisAlignment: MainAxisAlignment.center)
|> [
Center()
|> Padding(padding: EdgeInsets.all(5))
|> ColoredBox(color: Colors.red)
|> SizedBox(width: 50),
|> Text('Item 1'),
Text('Item 2'),
Text('Item 3'),
] |
@tenhobi The side effect on how much easier it would get to review UI PRs with Diffs is a bit plus |
I find this proposal very compelling. It looks amazing from a Flutter perspective. |
I agree the language should support beautiful Flutter code, but this solution doesn't look good imo. I'd claim state management must have special language support too (like Svelte had). @tenhobi Would it work? Reading the proposal:
I read var children = new List<Text>();
children.add(new Text('Hi')); I guess it could work, but the proposal would need to be expanded to have an "operator |>" and add it in the List class. |
@Wdestroier That would make all widgets not be @mraleph If we just threw out The Flutter Maintainers would just need to add |
@Wdestroier I don't understand what wouldn't work? It would work just as Flutter works today, you can just put a parameter marked as pipeable outside after the constructor/function call. Basically just a syntactic sugar, right? If you put it in print (just a stupid example), then: void print(pipeable String text);
print()
|> 'Hello world'; If you put it in Column, then class Column ... {
const Column({required pipeable List<Widget> children});
...
}
const Column()
|> [
Text('a'),
Text('b'),
]
Column()
|> [
const Text('a'),
Text('b = $value'),
] |
This has the opposite direction of #1246. This sounds like a way to place one argument outside of the argument list, presumably in order to indent it less. It feels similar to cascades to me, just at the argument list level. It's a way to keep doing something without indenting for each instance. Center()::child:
DecoratedBox(color: .red):: child:
Text(theText) or Center()
:::child: DecoratedBox(color: .red)
:: child: Text(theText) Maybe what is needed is just a different formatting, not a language feature? Probably hard to do something consistent. Still, something like Center(child:
DecoratedBox(
color: .red, child:
Text(theText)
)) which the formatter divines from some hint put on the |
@tenhobi Can you explain the green arrow? I took the original example: Center()
|> DecoratedBox(color: Colors.red)
|> Text('Hello world'); and changed the arrow direction: Center()
<| DecoratedBox(color: Colors.red)
<| Text('Hello world'); It looks more understandable, I guess. |
using |
@Wdestroier i corrected the code, it was a mistake (the last |> Padding...) |
Sorry if I step in, but imho using that syntax inside the Widget tree is very unreadable and confusionary. I'd love to have the pipe operator ( |
Making ALL dart code have the ability to pass arguments in 2 different ways:
would divide the Flutter/Dart community, which is one of the major drawbacks of the entire decorators proposals. (besides for the entire argument about how it's actually harder to understand blah blah blah) I don't want to see a Tabs vs Spaces fight for no good reason. Adding a
However from a language perspective I imagine this would require massive changes to the analyzer and all the code generation libraries. |
@dickermoshe That's not how the pipe operator works.
|
Some other ideas for the operator, e.g. Column(mainAxisAlignment: MainAxisAlignment.center, spacing: 8)
-> [
Center()
-> Padding(padding: EdgeInsets.all(5))
-> ColoredBox(color: Colors.red)
-> SizedBox(width: 50),
-> Text('Item 1'),
Text('Item 2'),
Text('Item 3'),
] or Center()
{ DecoratedBox(decoration: ...)
{ Padding(...)
{ Text('Hello world'); }}} |
I think we should indeed use the |
@orestesgaolin Let's not get caught up on semantics yet, we don't know if dads letting us get a dog so don't start coming up with names for him yet |
Yeah the name of that
A core problem is also how () are simplified. Changing the formatting wouldn't help that. There's already a new formating with similar ideas in mind coming to Dart 3.7, and it's not enough |
I do like the idea of viewing this as a cascade-like operator though. But a cascade-like operator would raise the topic of named VS positional parameters And the formatting is likely still doing to be very indented for that. I assume we'd have: Center()
:: child = DecotedBox(color: .red)
:: child = MyButton(); If we format everything on the same indent level, that'd raise the question of "which class are we initializing"? Unless that cascade operator would only allow a single initialization? But then that doesn't really feel cascade-like. So something more specialized makes sense IMO. It's important to remove those indents |
First two notes about the syntax:
I don't think everything needs to be a special syntax / language feature. Constructors are great because they are just a language feature, which exists in one form or another in most languages. Decorators (as explored by @loic-sharma and the team) are just extensions methods, which fundamentally also means they are just calls. Easy to understand again. FWIW we could (almost) experiment with this style without any special language features: class PipeWidget<P extends Widget, C extends Widget> extends StatelessWidget {
final P parent;
final C child;
const PipeWidget({required this.parent, required this.child});
@override
Widget build(BuildContext context) {
return wrap(parent, child);
}
}
extension WrapInWidget1<P extends Widget> on P {
PipeWidget<P, Widget> operator <<(Widget child) {
return PipeWidget(parent: this, child: child);
}
}
extension WrapInWidget2<P extends Widget, W extends Widget>
on PipeWidget<P, W> {
PipeWidget<P, Widget> operator <<(Widget innerChild) {
return PipeWidget(
parent: parent,
child: PipeWidget(parent: child, child: innerChild),
);
}
}
Widget wrap(Widget p, Widget w) {
return switch (p) {
Center() => Center(child: w),
_ => throw UnsupportedError('Unsupported parent widget: ${p.runtimeType}'),
};
} This makes abstract interface class SingleChildWidget<W extends SingleChildWidget<W>> implements Widget {
W wrap(Widget child);
}
abstract interface class MultiChildWidget<W extends MultiChildWidget<W>> implements Widget {
W wrap(List<Widget> children);
} |
I am aware that we could implement this with custom operators. IMO there's a lot of value in being able to write So being able to write There's also a major drawback to a custom operator: It wouldn't be backward compatible. If we can write |
FWIW as an alternative to
That is true. You can still make it work by having a placeholder: Not that I really think you should write code like this. I am simply suggesting to use this as a vehicle for experimentation: it is relatively easy to concoct an approximation of proposed syntax from existing language features, so you can experiment with this syntax today. Maybe even ship it as a package. Better implementation would likely requires changes to Flutter framework, but I can also imagine some variants which don't. I must be completely transparent that aesthetically this sort of syntax - no matter how it is implemented (be it a complicated concoction of extensions and helper classes or a language feature as proposed here) - does not really appeal to me in the slightest. It is too obscure and magical. Even more magical than Swift's result builders or whatever compiler plugin stuff Compose is doing. |
Agreed. I started working on a proof-of-concept. I'll share something later today with custom operators. I have a pretty good solution |
I can clearly see how we have less code but I also feel like we are really going backwards in terms of readability and understandability of the code at the same time and it just feels very much against the idea that Dart is an approachable language with this kind of magic syntax. I strongly dislike the tradeoffs and would really prefer that this kind of approach had a chance to be implemented using the existing language constructs like @mraleph suggested inside of a package as a way to test some of the assumptions here first before going too far down the path of considering this at the language level. |
@bivens-dev I'm working on a package which generates wrappers for common widgets. I will include an option to make a widget pipable. I will work on a If this gains traction then we can discuss this further. |
I still think this approach is better than the proposed Builder pattern, but the enums shorthands would be something required to make the code readable, so properties can be in just one line like Regarding the code samples, they're difficult to see mostly because we don't know what would be the right formatter, I just tried a bit with the dartpad sunflower example: Normal Samplehome: Scaffold(
appBar: AppBar(
title: const Text('Sunflower'),
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: SunflowerWidget(seeds),
),
const SizedBox(height: 20),
Text('Showing ${seeds.round()} seeds'),
SizedBox(
width: 300,
child: Slider(
min: 1,
max: maxSeeds.toDouble(),
value: seeds.toDouble(),
onChanged: (val) {
setState(() => seeds = val.round());
},
),
),
const SizedBox(height: 20),
],
),
),
), Pipe Sample home: Scaffold(
appBar: AppBar(
title: const Text('Sunflower'),
),
// Should be the pipe be formatted similar to "child:"?
body: Center()
|> Column(crossAxisAlignment: .center)
|> [ // Are the brackets still needed?
Expanded()
|> SunflowerWidget(seeds),
const SizedBox(height: 20),
Text('Showing ${seeds.round()} seeds'),
// Tried my best formatting this one
SizedBox(width: 300)
|> Slider(
min: 1,
max: maxSeeds.toDouble(),
value: seeds.toDouble(),
onChanged: (val) {
setState(() => seeds = val.round());
},
),
const SizedBox(height: 20),
],
),
Also it would be interesting to know what the Flutter team thinks about the drawbacks of the builder pattern pointed here, after all not implementing either of them is still an option. |
I really like this format the best so far. It's very clean visually and easy to type. |
Package Release: If you would like to try out this new syntax using your own flutter project, head to the pipeable section for a quick way to check this out. Gonna see if can fork a formatter and create a linter. |
I'm trying to see if I can get 90% of the way there with 10% of the work. The dart formatter doesn't resolve the AST so it won't be possible to format an object based on an object type. The dart_style library is really easy to understand. I hope to have a formatter working soon. However, the more I work on this, the more I hate the syntax. |
I think I understand where the idea a single literal comes from. The catch is that the composition cannot work well enough when we want to assemble the whole thing out of pieces. As an example, suppose we have a Column: //... part of a bigger literal
child: Column (
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: something,
textBaseline: something,
children: [
Expanded(...),
const SizedBox(...),
Text(...),
SizedBox(...),
]
). All nested widgets (Expanded, SizedBox, etc) are also written as (potentially large) literals. The program is difficult to read: the structure of the top widget gets lost in a forest of minor details (parameters, parentheses, brackets etc). Turns out, there's no obvious way to do that. E.g. we could try final _column = Column (
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: something,
textBaseline: something,
children: [
// same as above
]
); and then insert the reference to There's, of course, an option to resort to "partial function application", so that final _column(List<Widget> children) => Column (
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: something,
textBaseline: something,
children: children
); And then, while inserting the child: _column ([
Expanded(...),
const SizedBox(...),
Text(...),
SizedBox(...),
]
), This could work, but:
...
)
)
)
); Let's try to address the second problem. child: _column of [
Expanded(...),
const SizedBox(...),
Text(...),
SizedBox(...),
], This will eliminate the closing |
Assuming the language can (somehow) add support for the "of" clause for widgets with home: Scaffold(
appBar: AppBar(title: const Text('Sunflower')),
body: Center of
Column(crossAxisAlignment: CrossAxisAlignment.center) of [
Expanded of SunflowerWidget(seeds),
const SizedBox(height: 20),
Text('Showing ${seeds.round()} seeds'),
SizedBox(width: 300) of Slider(
min: 1,
max: maxSeeds.toDouble(),
value: seeds.toDouble(),
onChanged: (val) {setState(() => seeds = val.round());}
),
const SizedBox(height: 20),
],
), This program is almost 2 times shorter than the original. The savings come from two sources:
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// ...
]
) now becomes Column(crossAxisAlignment: CrossAxisAlignment.center) of [
// ...
] |
I think there is some confusion in this thread about how F#'s forward pipe operator works. It just passes the result of the left side to the function on the right side, so you can already do this in Dart with an operator overload. However, I don't think it makes sense in this scenario: Center()
|> DecoratedBox(color: Colors.red)
|> Text('Hello world'); Because However, we can do this in Dart already. It hinges on Dart tear-offs and operator overriding, which provide most of the the functionality of F#'s |> I think. DecoratedBox redDecoratedBox(Widget child) => DecoratedBox(
decoration: const BoxDecoration(color: Colors.red),
child: child,
);
Center center(Widget child) => Center(child: child);
final centered = (center | redDecoratedBox)(const Text('Hello world')) Here is a more complete example. You can try it out in Dartpad here. extension ForwardPipeOperator<T extends Widget, T2 extends Widget> on T
Function(Widget) {
/// Returns a new function that applies the given transformation to the result
/// of this function
T Function(T2) operator |(Widget Function(Widget) next) =>
(w) => this(next(w));
}
// These functions take advantage of Dart's tear-off syntax to achieve a
// a similar result to F#'s forward pipe operator
MaterialApp materialApp(Widget home) => MaterialApp(
debugShowCheckedModeBanner: false,
home: home,
);
Scaffold scaffold(Widget body) => Scaffold(
body: body,
);
Center center(Widget body) => Center(
child: body,
);
// This is the cool part
void main() => runApp(
(materialApp |
scaffold |
Rotating.new |
center)(const Text('Hello world')),
);
class Rotating extends StatefulWidget {
// ignore: public_member_api_docs, use_key_in_widget_constructors
const Rotating(this.child);
final Widget child;
@override
State<Rotating> createState() => _RotatingState();
}
class _RotatingState extends State<Rotating>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => AnimatedBuilder(
animation: _controller,
builder: (context, child) => Transform.rotate(
angle: _controller.value * 2 * pi,
child: Transform.scale(
scale: 0.8 + (_controller.value * 0.4),
child: child,
),
),
child: widget.child,
);
} There are some other similar approaches like this: test('Animated transformation chain', () {
final fade = const FlutterLogo(size: 100)
.pipe(rotatingBox)
.pipe(bounceTransition)
.pipe(center)
.pipe(fadeTransition);
final centerWidget = fade.child! as Center;
final bounce = centerWidget.child! as AnimatedBuilder;
final rotating = bounce.child! as AnimatedBuilder;
final logo = rotating.child! as FlutterLogo;
expect(logo.size, equals(100));
}); But, this is kinda inside out. @mraleph , this is why I think that it would be good to allow arbitrary operator symbols and with type arguments. We are limited by the current symbols. It would be nice to be able to specify the |> operator. |
I have been thinking about a possible formalization of the of trick suggested in this comment. Here's one way to do it. of-parametersAny function can declare one of its parameters as "of-parameter". of-parameters in WidgetsWrite a script that goes through Flutter codebase and makes every "child" and "children" parameter an of-parameter. Explicit partial applicationIf we want to partially apply RestrictionsThere are some obvious restrictions on partial application. E.g. to leave an of-parameter hanging from this declaration ExampleWith these changes, the example from the last comment will work. For better readability, I'll refactor it slightly final slider = Slider(
min: 1,
max: maxSeeds.toDouble(),
value: seeds.toDouble(),
onChanged: (val) {setState(() => seeds = val.round());}
);
...
home: Scaffold(
appBar: AppBar(title: const Text('Sunflower')),
body: Center of
Column(crossAxisAlignment: CrossAxisAlignment.center) of [
Expanded of SunflowerWidget(seeds),
const SizedBox(height: 20),
Text('Showing ${seeds.round()} seeds'),
SizedBox(width: 300) of slider,
const SizedBox(height: 20),
]
), For comparison, here's an original ("BEFORE") version Look Insidehome: Scaffold(
appBar: AppBar(
title: const Text('Sunflower'),
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: SunflowerWidget(seeds),
),
const SizedBox(height: 20),
Text('Showing ${seeds.round()} seeds'),
SizedBox(
width: 300,
child: Slider(
min: 1,
max: maxSeeds.toDouble(),
value: seeds.toDouble(),
onChanged: (val) {
setState(() => seeds = val.round());
},
),
),
const SizedBox(height: 20),
],
),
),
), NOTE: this construction is based on the same idea as OP, with less exotic syntax. The formalization is different though. |
An interesting perspective of looking into the above mechanism is considering it a variant of tear-off. |
I'm a bit late to the party, but this is super interesting. My personal opinion: a messy build method with many nested constructors is still going to be a messy build method after the indentation and/or brackets go away, and it might even become more difficult to visually parse. I believe the new "tall mode" formatter is already doing a fantastic job of mitigating the
I would love if we had a straightforward way to get rid of the explicit It'd be so nice to go from Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('okay'),
],
),
) to: Center(
Column(
mainAxisSize: MainAxisSize.min,
[
Text('Fabulous'),
],
),
) IMO the small tweak would be an aesthetic upgrade for every widget tree in existence. I'd be really excited about pursuing either #1076 or #2232, and I imagine it'd be a straightforward migration on the Flutter side: class Container extends StatelessWidget {
Container([Widget? $child], {Widget? child}) : child = $child ?? child;
final Widget? child;
} |
One feature that could help is "named positional arguments", being able to pass a positional argument by name instead of position. That does mean that the name becomes significant, so it may need opt-in syntax. Then you would be able to call, fx, It would be based on positional names behind visible in a function type or function signature, without being meaningful in subtyping. |
@nate-thegrate: Column has 10 optional parameters (including |
@lrhn someFunction(bool a, bool b){ }
someFunction(true, false) // Hard to understand
someFunction({required bool a,required bool b}){ }
someFunction(isUtc: true, isToday: false) // Easy to understand Allowing optional arguments to come 1st sounds like a great idea, but "named positional arguments" induces increased complexity. |
I believe the solution is for
// Current setup
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
const MultiChildRenderObjectWidget({super.key, this.children = const <Widget>[]});
}
class Column extends Flex {
const Column({super.key, super.children});
}
// Migration period
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
const MultiChildRenderObjectWidget([List<Widget>? $children], {super.key, List<Widget>? children})
: children = $children ?? children ?? const <Widget>[];
}
class Column extends Flex {
const Column([super.$children], {super.key, super.children});
}
// Migration complete
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
const MultiChildRenderObjectWidget([this.children = const <Widget>[]], {super.key});
}
class Column extends Flex {
const Column([super.children], {super.key});
} Due to how prevalent widgets like |
This is a very good point, especially in the context of boolean arguments. I'm glad we have the avoid_positional_boolean_parameters linter rule; perhaps sometime it could be added to the default recommended ruleset. |
As someone who proposed moving child/children as positional parameter as last parameter for a long time, I strongly support that we move the discussion in this direction. |
Reclassifying child/children as a positional parameter, even with the option to preserve 'named' syntax, won't solve any of the problems. What's the point? The number of lines in your code will remain the same; the ladder of closing |
I feel you, comrade. For a good portion of the past year I was dedicated to making the Flutter framework more concise; after 42 PRs I managed to get a total reduction of 4,926 LOC. But other things aside from line count have a big impact on readability, which is why (in my opinion) the ladder of closing brackets isn't necessarily a bad thing.
I personally feel strongly about this, but you (and others) certainly have a right to disagree 🙂 |
It significantly reduces tge noise in larger widget trees.
In #4211 (comment)
I made an example in the second code piece.
The repeated child/children doesn't add any informational value when we adopt the convention that the last parameter is always child/children.
Am 31. Jan. 2025, 21:56 +0100 schrieb Nate Wilson ***@***.***>:
… > What's the point? The number of lines in your code will remain the same
I feel you, comrade. For a good portion of the past year I was dedicated to making the Flutter framework more concise; after 42 PRs I managed to get a reduction of 4,926 LOC.
But other things aside from line count have a big impact on readability, which is why (in my opinion) the ladder of closing brackets isn't necessarily a bad thing.
> It'd be so nice to go from
> Center(
> child: Column(
> mainAxisSize: MainAxisSize.min,
> children: [
> Text('okay'),
> ],
> ),
> )
> to:
> Center(
> Column(
> mainAxisSize: MainAxisSize.min,
> [
> Text('Fabulous'),
> ],
> ),
> )
I personally feel strongly about this, but you (and others) certainly have a right to disagree 🙂
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
@escamoteur: I agree that explicit "child"/"children" is annoying. But I think removing it, though a good step (I like your example), is not enough. MyColumn(List<Widget> children) => Column(
mainAxisAlignment: MainAxisAlignment.start,
spacing: 0.1,
children: children); // leaving only "children" as a parameter! Then I could use MyColumn in the literal. MyColumn([
Expanded(
ClipPath(
// etc.. But:
|
Absolutely! If you found yourself repeatedly using the same class MyColumn extends Column {
const MyColumn([super.children]) : super(spacing: 1);
} (The same is possible today, but it'd go against the existing constructor signature.) |
What you use there is a factory function that creates parts of your widget tree, which is generally seen as an antipattern. But after some googling I just created a discussion on this aspect https://forum.itsallwidgets.com/t/performance-of-widgets-or-helper-methods-and-const-widgets/2459
Because I'm not sure if it really has so many downsides.
In general destructuring a large widget tree this way is a good idea although I tend to use stateless widgets, mainly so that I can use watch_it to only rebuild the least necessary part of my tree.
Am 31. Jan. 2025, 22:48 +0100 schrieb Tatumizer ***@***.***>:
… @escamoteur: I agree that explicit "child"/"children" is annoying. But I think removing it is not enough.
I'd like to be able to compose the literal from the smaller building blocks but preserve the overall nested structure.
And indeed. I can do it today:
MyColumn(List<Widget> children) => Column(
mainAxisAlignment: MainAxisAlignment.start,
spacing: 0.1,
children: children); // leaving only "children" as a parameter!
Then I could use MyColumn in the literal.
MyColumn([
Expanded(
ClipPath(
// etc..
But:
1. for some reason, this style of programming is not common. Or maybe it can become more common if children becomes a positional parameter? (Please comment).
2. Having some sugar while defining MyColumn won't hurt.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
If it indeed turns out that we can't build a big thing out of smaller things without dramatically affecting performance, then we have an issue 10 times worse than the one being discussed on this thread. 😄 |
a widget extensions give us the benefits of the pipe operator examples shown above, are easier to type, and are already widely in use in projects (either a couple extensions or a full suite of styling and layout extensions) in short: adding a |
@lukepighetti can you give an example of what the syntax for a widget tree would look like with your proposal to use a let extension? |
you wouldn't use a let extension with Widgets unless you had a function with a single positional parameter with type Widget which is extremely uncommon. you'd use widget extensions. there are some languages (mintlang) that perform algebra on the arguments passing through the unspecified parameters to the next method in the chain but this is complicated and confusing |
It feels like a dejavu for a feature supported in several other languages (Swift, Kotlin, Groovy), which essentially allows you to move the last parameter of a lambda type out of the parameter list into a code block after method call statement like Dart's lambdas having two forms
|
On the other hand, if
|
Problem:
One common complaint with Flutter is how widget trees tend to get very nested.
There are various attempts at solving this, such as using the builder pattern. Cf: https://www.reddit.com/r/FlutterDev/comments/hmwpgm/dart_extensions_to_flatten_flutters_deep_nested/
More recently, this was suggested during the Flutter in production livestream
But relying on chained methods to solve the problem has numerous issues:
const
constructorsa(b(c()))
becomesc().b().a()
Using builders, we need to define both a class, and an extension method that maps to said class:
Class()
and sometimes as.class()
, which feels a bit inconsistentListView
hasListView()
andListView.builder()
. But as methods, at best we'd have.listView()
vs.listViewBuilder()
.myWidget()
redirects to the extension, and renaming.myWidget()
won't renameMyWidget()
Proposal:
I suggest introducing two things:
Such keyword could be used on any parameter. Positional or named, and required or optional. But the keyword can be specified at most once per constructor/function, as only a single parameter can be piped.
The idea is that one parameter per constructor/function definition can be marked with
pipable
. For example:When a parameter is marked as such, the associated constructor/function becomes usable in combination with a new "pipe" operator.
We could therefore write:
Internally, this would be strictly identical to:
In fact, we could support the
const
keyword:Note:
Since in our
Center
example, thechild
parameter is required, it would be a compilation error to not use the pipe operator.As such, this is invalid:
Center();
Similarly, the
|>
operator isn't mandatory to use ourCenter
widget. We are free to use it the "normal" way and writeCenter(chid: ...)
.In a sense, the combo of the
pipable
keyword +|>
operator enables a different way to pass a single parameter, for the sake of simple chaining.Conclusion:
This should solve all of the problems mentioned at the top of the page.
const
on a complex widget treeAs a bonus, the syntax is fully backward compatible. It is not a breaking change to allow a class to be used this way, as the class can still be used as is this feature didn't exist.
The text was updated successfully, but these errors were encountered: