Skip to content
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

Implicit member expressions #1062

Closed
MarcelGarus opened this issue Jul 2, 2020 · 2 comments
Closed

Implicit member expressions #1062

MarcelGarus opened this issue Jul 2, 2020 · 2 comments
Labels
feature Proposed language feature that solves one or more problems state-duplicate This issue or pull request already exists

Comments

@MarcelGarus
Copy link
Contributor

MarcelGarus commented Jul 2, 2020

When referencing either a class's static member or an enum variant, the member has to be accessed using TypeName.member.
If type inference determined the required type, in many cases it's obvious what the type should be. In those cases, being able to omit the type name when referencing a member would make for much shorter code, just .member in this case.

This is similar to the "Implicit member expressions" feature in Swift.

Here are some usage examples:

enum Ripeness {
  unripe,
  ripe,
  overripe,
}

Ripeness bar = .unripe; // works
var foo = Ripeness.ripe; // implicit enum variants don't work here because the type is not clear by context
foo = .overripe; // works too because the base type is clear by now.

class Fruit {
  const Fruit({this.name, this.ripeness});

  String name;
  Ripeness ripeness;

  static const apple = Fruit(name: 'apple', ripeness: .ripe); // works because a Ripeness is expected
}

abstract class OtherFruits {
  static const banana = Fruit(name: 'banana', ripeness: .unripe);
  static const kiwi = Fruit(name: 'kiwi', ripeness: .overripe);
}

Fruit someFruit = .apple; // works because it's clear a Fruit is expected and Fruit has a member named apple
someFruit = OtherFruits.kiwi; // implicit enum variants don't work, because the base type is different

if (someFruit.ripeness == .ripe) { // works because the type of someFruit.ripeness is clear
  print('The ${someFruit.name} is ripe.');
}

// makes switch more succinct
switch (someFruit.ripeness) {
  case .unripe:
  case .ripe:
    print('waiting…');
    break;
  case .overripe:
    print('waited too long');
    break;
}

// can't be used here because the type can't be determined by context – this could be a List of Object
final unripeFruits = [Fruit.apple, OtherFruits.kiwi, OtherFruits.banana]
  .where((fruit) => fruit.ripeness == .unripe);

This would be useful in lots of places in the real world – basically everywhere where enums or static special values are used.
In Flutter, for example, one could imagine being able to write

// inside a build method
return Column(
  mainAxisSize: .max,
  mainAxisAlignment: .end,
  crossAxisAlignment: .start,
  children: <Widget>[
    Text('Hello', textAlign: .justify),
    Row(
      crossAxisAlignment: .baseline,
      textBaseline: .alphabetic,
      children: <Widget>[
        Container(color: Colors.red), // doesn't work here, because Colors != Color
        Align(
          // works, because bottomCenter is a static const on Alignment, the expected type
          alignment: .bottomCenter,
          child: Container(color: Colors.green),
        ),
      ],
    ),
  ],
);

Edge cases

The following case is worth considering:

class Fruit {
  // ... (just like above)

  static const yummyFruits = [apple, OtherFruits.kiwi, OtherFruits.banana];
}

List<Fruit> smoothie = [
  .apple,
  ....yummyFruits, // lo and behold, the quadruple dot it coming for you
];

I'm not sure yet whether we'd need to handle this in a special way or how to handle this – maybe force a space before the expression as in ... .yummyFruits?
Semantically, there is no ambiguity either way, so just making sure dartfmt produces a useful result is probably the most viable option.

What are the benefits/dangers?

Exhaustive variants and default values both need to be expressed in a lot of scenarios. This feature would impact all of those cases.

Regarding readability concerns, it's easy to come up with an example such as this:

bool shouldBuy(Ripeness ripeness) => ripeness != .overripe;
shouldBuy(.unripe);

That is a valid concern – on the caller side, it's no longer clear that the parameter is a Ripeness. But the same problem has already been solved for non-enum parameters: Named parameters are used in cases where positional parameters don't clearly express the intent. Additionally, you can still use the old syntax of shouldBuy(Ripeness.unripe), if you prefer.

In the long term, I believe, linting rules should be put in place to encourage the use of the new, shorter syntax.

Social impact / transition

To experienced Dart developers, this saves some keystrokes, but more importantly, makes code more succinct and readable by reducing visual clutter.
Swift developers could continue using this feature.
New developers coming from other non-Swift-languages could continue writing code as usual but could get gently nudged towards using this feature by linting rules.
Novice Dart developers reading code are maybe startled at first by the new syntax. By definition, this syntax is only used where the type can be inferred from context though, so in most/all cases, the intent of the usage is still clear.

@MarcelGarus MarcelGarus added the feature Proposed language feature that solves one or more problems label Jul 2, 2020
@mnordine
Copy link
Contributor

mnordine commented Jul 3, 2020

See #357

@lrhn
Copy link
Member

lrhn commented Jul 3, 2020

This is indeed a duplicate of #357.

(The [....foo] will work as parsing is currently specified. The tokenizer will pick the longest token while scanning from start to en, so it will see ... first and . second, which is what you want. The formatter could then perhaps introduce a space "for readability").

@lrhn lrhn closed this as completed Jul 3, 2020
@lrhn lrhn added the state-duplicate This issue or pull request already exists label Jul 3, 2020
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 state-duplicate This issue or pull request already exists
Projects
None yet
Development

No branches or pull requests

3 participants