Language: hope for do expression

Created on 13 Dec 2018  Â·  9Comments  Â·  Source: dart-lang/language

I think do expression is very useful in flutter's html-like code.

And do expression shouldn't be just anonymous function calling even though their code length are very near, because using anonymous function calling, the generator status can not pass down.

please have a look at https://github.com/tc39/proposal-do-expressions/issues/37

request

Most helpful comment

Could you please provide a full Flutter code sample showing how do expressions would work?

A do expression is basically just a way to put a block of statements in a context where an expression is expected:

print(do {
  var a = 1;
  var b = 2;
  a + b
}); // Prints "3".

It's like an IIFE, except that IIFE's don't compose correctly with async, sync*, break, and continue.

Interestingly, we are considering adding a construct like this as part of the intermediate language that Dart is compiled to. See: https://github.com/dart-lang/language/pull/127#discussion_r241355291

It would be handy to have as a meta-syntax for specifying language features that are syntactic sugar. I'm not sure how useful it would be as a user-visible syntax feature.

All 9 comments

I don't read JSX very well, but semantically it looks similar to the argument blocks idea.

Could you please provide a full Flutter code sample showing how do expressions would work?

Could you please provide a full Flutter code sample showing how do expressions would work?

A do expression is basically just a way to put a block of statements in a context where an expression is expected:

print(do {
  var a = 1;
  var b = 2;
  a + b
}); // Prints "3".

It's like an IIFE, except that IIFE's don't compose correctly with async, sync*, break, and continue.

Interestingly, we are considering adding a construct like this as part of the intermediate language that Dart is compiled to. See: https://github.com/dart-lang/language/pull/127#discussion_r241355291

It would be handy to have as a meta-syntax for specifying language features that are syntactic sugar. I'm not sure how useful it would be as a user-visible syntax feature.

This is one usecase in JSX:

return (
  <nav>
    <Home />
    {
      do {
        if (loggedIn) {
          <LogoutButton />
        } else {
          <LoginButton />
        }
      }
    }
  </nav>
)

Without a do expression, you either have to write:

  • a ternary (which may be less readable)
  • an IIFE (which is more ugly/unusual)
  • a function (which may give unesssary abstraction/indirection)
  • or factor it out above (defeats the goal to have a single nested expression tree that you can read from top-to-bottom and outside-in)

For Flutter, if you go with the last option, you probably would write something like this:

var button;
if (loggedIn) {
  button = LogoutButton();
} else {
  button = LoginButton();
}
return Nav(
  children: [
    Home(),
    button,
  ]
);

The control flow collection proposal tries to solve this in the following way:

return Nav(
  children: [
    Home(),
    if (loggedIn)
      LogoutButton()
    else 
      LoginButton(),
  ]
);

If Dart would adopt do expressions, it would look like this:

return Nav(
  children: [
    Home(),
    do {
      if (loggedIn) {
        LogoutButton();
      } else { 
        LoginButton();
      }
    },
  ]
);

The Flow Collection Proposal is shorter, and also allows to add "Nothing" to the collection, when there is no else.

However do expression can also be used in other contexts, for example, if you want type inference, no- nullability and immmutability in the following code:

  factory Car(Position start, bool horizontal, int length) {
    var end;
    if (horizontal) {
      end = start + new Position(length - 1, 0);
    } else {
      end = start + new Position(0, length - 1);
    }

    var positions;
    if (length == 2) {
      positions = <Position>[start, end];
    } else {
      if (horizontal) {
        positions = <Position>[start, start + new Position(1, 0), end];
      } else {
        positions = <Position>[start, start + new Position(0, 1), end];
      }
    }
    return new Car._(start, end, horizontal, length, positions);
  }

With do expression it can be written like:

  factory Car(Position start, bool horizontal, int length) {
    final end = do {
      if (horizontal) {
        start + new Position(length - 1, 0);
      } else {
        start + new Position(0, length - 1);
      }
    }

    final positions = do {
      if (length == 2) {
        <Position>[start, end];
      } else {
        if (horizontal) {
          <Position>[start, start + new Position(1, 0), end];
        } else {
          <Position>[start, start + new Position(0, 1), end];
        }
      }
    }
    return new Car._(start, end, horizontal, length, positions);
  }

There are complications to allowing statements with expressions. Currently, the only control flow an expression can do is throwing (which is also the only non-local control flow).
Local control flow, like break/continue/return, can only happen at the statement level (and await is an exception because it is a kind of control flow, and local, and allowed in expressions, which is one of the reasons compiling async functions is hard).

If we allow expression-blocks like do { statements; expressionStatement; }, it's not clear that we would allow them to do local control flow. No var x = do { if (something) return 42; "didn't return"; } because that would potentially complicate some things (and throw readability out the window).

@lrhn This question is also discussed here:

https://github.com/tc39/proposal-do-expressions/issues/30

return Nav(
  children: [
    Home(),
    do {
      if (loggedIn) {
        LogoutButton();
      } else { 
        LoginButton();
      }
    },
  ]
);

For that to work, there would have to be some implicit magic that the value usually discarded by a statement expression would somehow propagate out to the surrounding context. How would the language know that you want to keep the values returned by LogoutButton() and LoginButton(), but ignore the value returned by, say, print:

return Nav(
  children: [
    Home(),
    do {
      print("Just some debug code.")
      if (loggedIn) {
        LogoutButton();
      } else { 
        LoginButton();
      }
    },
  ]
);

Also, what happens if the do expression tries to emit multiple values, but is used in a context where only one is expected? What would this do:

var wat = do {
  LogoutButton();
  LoginButton();
};

I don't know if flutter support concept of render-props-component. If it do, then do expression has use cases like:

return Nav(
  children: [
    Home(),
    do {
      // Auth is a RenderPropsWidget that its child should be a callback function, and pass a boolean login status to that function
      var loggedIn = yield Auth();
      if (loggedIn) {
        LogoutButton();
      } else { 
        LoginButton();
      }
    },
  ]
);

And we can leave out the keyword do. Many expression-first-classed languages like rust leave out do.

do is not important, expression-first is important.

For that to work, there would have to be some implicit magic that the value usually discarded by a statement expression would somehow propagate out to the surrounding context.

I couldn’t find really good documentation, but I believe it works similar as in “expression orientated languages”, the last executed statement expression is value that the do expression evaluates to.

You can try out how it works here. It is included in the babel stage-1 preset.

var end;
if (horizontal) {
  end = start + new Position(length - 1, 0);
} else {
  end = start + new Position(0, length - 1);
}

Interestingly, in Kotlin you can do

val end = if (horizontal) {
    start + new Position(length - 1, 0);
} else {
    start + new Position(0, length - 1);
}

or its more preferred variant

val end = when {
    horizontal -> start + new Position(length - 1, 0);
    else -> start + new Position(0, length - 1);
}
Was this page helpful?
0 / 5 - 0 ratings