Skip to content

Add infer keyword #2173

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

Open
AlexanderFarkas opened this issue Mar 28, 2022 · 13 comments
Open

Add infer keyword #2173

AlexanderFarkas opened this issue Mar 28, 2022 · 13 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@AlexanderFarkas
Copy link

AlexanderFarkas commented Mar 28, 2022

Let:

abstract class Entity<T> {
  T get id;
}

class EntityStore<T extends Entity<K>, K> {
  T getById(K id) {
    ...
  }
}

class Customer extends Entity<int> {
  final int id;
  Customer(this.id);
}

Calling Code:
final store = EntityStore<Customer, int>();

But type parameter K could already be inferred from T:
final store = EntityStore<Customer>(); // this doesn't work

I propose infer keyword for type parameters that could be inferred from other type parameters:
class EntityStore<T extends Entity<K>, infer K> {}

@AlexanderFarkas AlexanderFarkas added the feature Proposed language feature that solves one or more problems label Mar 28, 2022
@lrhn
Copy link
Member

lrhn commented Mar 28, 2022

Should type bounds really be type patterns?

class EntityStore<T extends Entity<var K>> { ... }

@munificent

@eernstg
Copy link
Member

eernstg commented Mar 28, 2022

We've had similar discussions previously, e.g., #620 (comment).

@munificent
Copy link
Member

That's a really interesting idea. What about in parameter lists?

foo(List<final T> someList) { ... }

Deciding when to use a generic method and when to use a type pattern seems like a tricky and subtle design choice.

@leafpetersen
Copy link
Member

What about in parameter lists?

My initial take would be to avoid this, and instead add the first class version of extractTypeArguments that we've discussed. I think the latter provides a strict super set of this functionality?

@lrhn
Copy link
Member

lrhn commented Mar 29, 2022

The distinction between foo<T>(List<T> someList) { ... } and foo(List<final T> someList) { ... } would be that the latter destructures the actual runtime type of the argument and extracts type arguments. The former takes a separate type as argument, which the list's element type must be a subtype of.

The pattern-in-parameter is no different from doing foo(List<dynamic> someList) { if (someList is List<final T>) { ... }}, and can possible even be desugared to that.

There is no way to separate the List<final T> type from the actual runtime type, which is also why there is no type parameter for the function.

The next question would be whether you can do add(List<final T> list, T value) { list.add(value); }. You probably cannot. That's dependent typing and you can't statically ensure its safety, so it can't be allowed in a binding pattern which relies on static type soundness. (Same likely applies to doing type matching inside record matching, the type can't be used again in the same match if it's a binding match.) It's probably possible to use it in a matching pattern, because then it can fail to match.

We probably can allow the return type to depend on the pattern-matched type variable, like T mid(List<final T> list) => list[list.length ~/ 2];, because the return statements happen inside the scope of the binding.

It might very well be that some generic methods would be better written as pattern-matching type-extractors.
I can't remember one right now, though. (A lot of generic methods are only generic in order to allow an existential type, like void apply<A>(void Function(A) f, A arg) => f(arg);. Those still need the type parameter, because the type affects both parameters.)

So, a pattern like List<final T extends num> would both constrain the type that it can match and extract the runtime element type if it matches.

@munificent
Copy link
Member

What about in parameter lists?

My initial take would be to avoid this, and instead add the first class version of extractTypeArguments that we've discussed. I think the latter provides a strict super set of this functionality?

Yeah, the patterns proposal already covers this use case. But thinking about using type patterns in bounds made me wonder about using them in other places where type parameters can occur when binding is happening and parameter lists are another example of that. (This, of course, also raises the question of allowing other kinds of patterns in parameter lists.)

The pattern-in-parameter is no different from doing foo(List<dynamic> someList) { if (someList is List<final T>) { ... }}, and can possible even be desugared to that.

Yeah, I think this is the right approach. Allowing type patterns directly in the parameter list seems like a can of worms.

@lrhn
Copy link
Member

lrhn commented Mar 29, 2022

The pattern-in-parameter is no different from doing foo(List<dynamic> someList) { if (someList is List<final T>) { ... }}, and can possible even be desugared to that.

Yeah, I think this is the right approach. Allowing type patterns directly in the parameter list seems like a can of worms.

I was actually using that as an argument for allowing patterns in parameter lists. Pattern-parameters are statically typed irrefutable binding constructs, just like what variable declarations become with patterns, so I don't think there should be any problem, and forcing people to declare the un-patterned variable and then immediately pattern-match it inside the function, is just adding overhead.

@pedromassango
Copy link

I think we should close this in favor of #620 as they address the exact same issue but with different proposed solutions. Or the other way around.

@pedromassango
Copy link

btw, is this ticket (or #620) something that is being considered/talked about by the team? 👀

@TimWhiting
Copy link

Cross referencing with the issue related to patterns #2221

@shtse8
Copy link

shtse8 commented Jan 9, 2023

In my use case, I want to wrap a list in generic type with a class without knowing what inner type is.

T wrap(T obj) {   
  if (obj is List) obj = ReactiveList(obj) as T
  return obj;
}

where ReactiveList<T> extends List<T>.

Resulting in:

_CastError (type 'ReactiveList<dynamic>' is not a subtype of type 'List<int>' in type cast.

Is it possible to infer the type?

T wrap(T obj) {   
  if (obj is List<var V>) obj = ReactiveList<V>(obj) as T
  return obj;
}

@AlexanderFarkas
Copy link
Author

AlexanderFarkas commented Jan 9, 2023

@shtse8 Why not just

ReactiveList<T> wrap<T>(Iterable<T> list) {
  return ReactiveList(list);
}

@shtse8
Copy link

shtse8 commented Jan 9, 2023

@shtse8 Why not just

ReactiveList<T> wrap<T>(Iterable<T> list) {
  return ReactiveList(list);
}

because T is probably other types, not only list.
wrap is one of the methods in class. the value stored in the class is generic type but I just need to wrap it if the type is List.

class Ref<T> {
  T value;
  Ref(T value) : this.value = _wrap(value);

  T _wrap(T obj) {   
    if (obj is List) obj = ReactiveList(obj) as T
    if (obj is Map) obj = ReactiveMap(obj) as T
    return obj;
  }
}

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

8 participants