Skip to content

Instantly share code, notes, and snippets.

@yjbanov
Last active June 28, 2018 16:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yjbanov/0cdaf2c8ad3195bf49ab7cccffb69efe to your computer and use it in GitHub Desktop.
Save yjbanov/0cdaf2c8ad3195bf49ab7cccffb69efe to your computer and use it in GitHub Desktop.
Conditional wrapping operator for Dart
// This build method attempts to conditionally insert a
// DecoratedBox widget into the widget hierarchy. Roughly
// speaking we want the widget tree to be like this:
//
// if (someCondition) {
// Container > DecoratedBox > Container
// } else {
// Container > Container
// }
build(_) {
Widget result = Container(
color: Colors.blue,
);
if (someCondition) {
result = DecoratedBox(
color: Colors.green,
child: result,
);
}
result = Container(
color: Colors.red,
child: result,
);
return result;
}
// What if we had two operators `wrap` and `unwrap`. We could
// call the pair "conditional wrapping operator".
//
// `wrap`/`unwrap` are applied to the beginning of an expression.
//
// In the AST of an expression `wrap` is an ancestor of an
// `unwrap`. They always come in pairs. Dangling `wrap`/`unwrap`
// are not allowed.
//
// Like `if`, you supply a boolean expression to the `wrap`
// operator. Everything between `wrap` and the nearest
// descendant `unwrap` is included only when that expression is
// `true`. If the expression is `false` the sub-expression marked
// by `unwrap` is hoisted up to the nearect ancestor `wrap`.
build(_) {
return Container(
color: Colors.red,
child: wrap (someCondition) DecoratedBox(
color: Colors.green,
child: unwrap Container(
color: Colors.blue,
),
),
);
}
@lrhn
Copy link

lrhn commented Jun 8, 2018

Alternatively, and possible without language change, make the constructor a factory function with a condition argument:

return Container(
  color: Colors.red,
  child: DecoratedBox.onlyIf(someCondition,   // If true, returns child wrapped, otherwise returns child.
    color: Colors.green,
    child: Container(
      color: Colors.blue,
    ),
  ),
);

That requires an alternative version of every constructor, and it's a function, not a constructor, since it has to return the child type.
On the other hand, the child type must be valid at that point where we want to omit something anyway, so we have to handle that type discrepancy in either case.

The conditional wrapping here looks very much like a higher-order abstraction.
You have a child, you have a function that you call on that child conditionally. A helper function could be:

T conditionally<T>(bool test, T action(T defaultValue), T defaultValue) => test ? action(defaultValue) : defaultValue;
...
return Container(
  color: Colors.red,
  child: conditionally(
    someCondition, 
    (child) => DecoratedBox(
      color: Colors.green,
      child: child,
    ),
    Container(
      color: Colors.blue,
    ),
  ),
);

Dart has first class functions, so we can already put a value into a new context. If we add new syntax, it should probably be possible to desugar it to something like this.

@yjbanov
Copy link
Author

yjbanov commented Jun 28, 2018

@lrhn, these are interesting ideas!

One good property of the onlyIf trick is that it retains the tree structure of the whole expression. Its biggest disadvantage though is that it seems to only work if you want to conditionally inject exactly one intermediate widget. That would make it not very useful.

The conditionally method is better semantically as it supports conditionally injecting a range of intermediate widgets. However, it breaks the tree structure visually by detaching the child expression from the main tree. Also, I'm afraid that using higher-order functions may be bad for performance, as we make extra function calls, and allocate a closure. I'd be less worried if I knew that in the end it all desugared into a series of if blocks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment