dart-lang / language

Design of the Dart language
Other
2.65k stars 201 forks source link

"Infer this type" for explicit generic types. #3510

Open water-mizuu opened 9 months ago

water-mizuu commented 9 months ago

What I'm thinking is simple. In the case where a function or class has multiple generic parameters, we should be allowed to explicitly say that the type system should infer the other arguments on its own. For example:

class ChangeNotifier {
    Prop select<Obj, Prop>(Prop Function(Obj) selector) => selector(this);
}

void main() {
  var notifier = // code that gets a notifier.
  /// For instance, this [name] property can be a String, so it's inferred as [notifier.select<SomeModel, String>(...)]
  var name = notifier.select<SomeModel, ?>((m) => m.name);
  /// Another possibility:
  var name = notifier.select<SomeModel, _>((m) => m.name);
}

I guess we can always just specify the type in the closure itself, but it might still prove useful in other constructs. For now, this is what I find myself using it most for.

srawlins commented 9 months ago

Today, you could do this with: var name = notifier.select((SomeModel m) => m.name);. Both type variables should be inferred.

lrhn commented 9 months ago

The easy way to write this is to have each subtype implement select again:

class SomeModel extends ChangeNotifier {
  final String name;
  SomeModel(this.name);
  R select<R>(R Function(SomeModel) selector) => selector(this);
}

Then you don't need to infer the type of the model.

If the argument is always the type itself, then Dart doesn't have self-types. The traditional way to make covariant self-references is a type parameter:

abstract class ChangeNotifier<N extends ChangeNotifier<N>> {
  R select<R>(R Function(N) selector) =>
      selector(this as N); // Unsafe if used incorrectly
}

class SomeModel extends ChangeNotifier<SomeModel> {
  final String name;
  SomeModel(this.name);
}

void main() {
  // Works both at type SomeModel and ChangeNotifier<SomeModel>
  ChangeNotifier<SomeModel> notifier = SomeModel("Baba Yaga");

  // Infers `m` to have type SomeModel, name to have type String.
  var name = notifier.select((m) => m.name);
  // The `name` is a `String`
  print(name.substring(0, name.indexOf(' ')));
}

Inferring partial type arguments isn't a bad idea. We can do it for all of them, we should be able to do it for some. I'd go with just just eliding the omitted type arguments, although the usability probably isn't optimal:

   notifier.select<SomeModel,>(...); // Trailing comma means an omitted type argument

So you can do Map<String,> map = {"a": 1, "b": 2.5}; and infer num as value type, or Map<, String> map = {1: "a", 1.5: "b"} and infer num as key type.

Or, we can allow curried type arguments:

  R select<M><R>(R Function(M) selector) => selector(this as M);

so you can do select<SomeModel>(...) and infer the second set of type arguments.

Or optional type arguments:

  R select<M, [R]>(R Function(M) selector) => selector(this as M);

and you can call as select<SomeModel> or Select<SomeModel, String>, but only if the author allowed it. Which is a bit weird, since omitting doesn't actually do anything, there is no default type chosen if you do, which would be another fancy feature. So, probably not a good idea by itself.

rubenferreira97 commented 9 months ago

I still think #170 is a good overall proposal because it enables a more complete feature set.

Example:

var name = notifier.select<SomeModel, final Inferred>((m) => m.name);
var name = notifier.select<SomeModel, final _>((m) => m.name);
var name = notifier.select<SomeModel, _>((m) => m.name); // We could treat `_` as `final _`, `__` as `final __`, etc..

// This enables nice features like:
if (Inferred is num) // Inferred <= String

// Not related with this issue but with this proposal maybe we could get some syntax of this sort to check types:
// List<final T> list = [1,2,3];
// if (T <= num) // `is`
// if (T == num) // `exactly`
// if (T >= num) // `super`
// if (T < num) // `is` excluding `exactly`
// if (T > num) // `super` excluding `exactly`