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

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

Open
water-mizuu opened this issue Dec 12, 2023 · 3 comments
Open

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

water-mizuu opened this issue Dec 12, 2023 · 3 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@water-mizuu
Copy link

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.

@water-mizuu water-mizuu added the feature Proposed language feature that solves one or more problems label Dec 12, 2023
@srawlins
Copy link
Member

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

@lrhn
Copy link
Member

lrhn commented Dec 24, 2023

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
Copy link

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`

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
Projects
None yet
Development

No branches or pull requests

4 participants