Open water-mizuu opened 9 months ago
Today, you could do this with: var name = notifier.select((SomeModel m) => m.name);
. Both type variables should be inferred.
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.
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`
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:
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.