Language: Conditional statements

Created on 25 Oct 2018  路  10Comments  路  Source: dart-lang/language

Not sure if 'conditional statements' is the right terminology but frequent issues while using Dart as UI are as follows:

Widget build(BuildContext context) {
  return CupertinoPageScaffold(
    child: ListView(
      children: [
        PageHeader(),
        isSignedIn ? SignInButton : justDon'tDoAnything
        RestOfThePage()
      ],
    ),
  );
}

In the case above, we don't actually want to return null since null has a different semantic meaning as one of the list elements.

There are of course imperfect work-arounds such as appending ..where((Widget item) => item != null) or returning : SomeFlutterWidgetThatDoesn'tDoAnythingInTheListView but they're still impedances to Dart as UI.

Another example is

WidgetA( // <= constructor
  paramA: overrideValueA,
  paramB: ifIShouldOverride ? overrideValueB : justDon'tDoAnythingAndLeaveConstructorDefaultWhichMayBeDifferentFromNull
)

Again, the present workaround solution is to wrap the whole constructor and invoke it differently twice, but it would be great if this can be expressed succinctly in the spirit of #47

request

Most helpful comment

@yjbanov in order for the solution to work properly with default parameter values, it needs to be builtin. We can't do that with a user-defined enum.

Also, FWIW, I just remembered a clever approach used by some JS libraries. It uses the list spread operator like this:

Widget build(BuildContext context) {
  return CupertinoPageScaffold(
    child: ListView(
      children: [
        PageHeader(),
        ...(isSignedIn ? [SignInButton] : [])
        RestOfThePage()
      ],
    ),
  );
}

It works, and I think it's better than extracting the children list into a variable, but is still not ideal.

All 10 comments

cc @munificent

I very strongly recommend to always let explicitly passing null mean the same thing as omitting an argument. Dart doesn't enforce that. I hope it eventually will (e.g., if we get non-nullable types, it might mesh well with that).

Until then, it would be possible to reuse the if syntax of control-flow-elements for arguments as well.

passing null mean the same thing as omitting an argument.

How would that work in the presence of default values?

I believe we have cases in Flutter where we have arguments where "null" is a reasonable value to pass, and where "null" is not the default. It's very hard to grep for these cases though and I don't remember any off-hand.

If I properly understood Ian's comment, we have 2 patterns:

  1. Things are built the way the immutable Widgets are constructed. If a default border argument defaults to a particular type of border, passing null explicitly means let it not have a border.
  2. Defaults come in later when concretely building the subtree the immutable Widget was meant to represent and we do things like InnerWidgetThatWeCompose(border: border ?? kSomeDefaultBorder).

Here, option 1 is great because an immutable UI representation actually represents the UI it's mean to represent without additional implicit runtime interpretations. But option 1 introduces the issue described in the original post.

Despite all the problems that undefined causes in JavaScript, this is one case where it provides a nice solution.

@lrhn:

explicitly passing null mean the same thing as omitting an argument. Dart doesn't enforce that. I hope it eventually will

So does that mean that, say, f will print 5 below twice?:

void f([int x = 5]) {
  print(x);
}
void main() {
  f(); // 5
  f(null); // 5
}

It would be really weird if null means one thing elsewhere in the language, but means omit here.

I think the use-cases listed in this discussion, where null or undefined are proposed as solutions, seem to actually want a more direct way to express more states a variable can be in other than the range of values in the normal state. I don't know what the most idiomatic approach would be for Dart, but that's what Rust and Swift use enums for.

@yjbanov in order for the solution to work properly with default parameter values, it needs to be builtin. We can't do that with a user-defined enum.

Also, FWIW, I just remembered a clever approach used by some JS libraries. It uses the list spread operator like this:

Widget build(BuildContext context) {
  return CupertinoPageScaffold(
    child: ListView(
      children: [
        PageHeader(),
        ...(isSignedIn ? [SignInButton] : [])
        RestOfThePage()
      ],
    ),
  );
}

It works, and I think it's better than extracting the children list into a variable, but is still not ideal.

Was this page helpful?
0 / 5 - 0 ratings