Open
Description
cross reference : felangel/bloc#560
currently in dart, we have to explicitly specify a type parameter for generic types, even when they can be inferred.
e.g. this is a valid class definition
class Bloc<TEvent,TState> {}
class BlocBuilder<TBloc extends Bloc<dynamic,TState>,TState> {}
class TestBloc extends Bloc<String,String> {}
class TestBlocBuilder extends BlocBuilder<TestBloc,String> {}
but this isn't
class Bloc<TEvent,TState> {}
class BlocBuilder<TBloc extends Bloc<dynamic,TState>> {}
class TestBloc extends Bloc<String,String> {}
class TestBlocBuilder extends BlocBuilder<TestBloc> {}
you get an error at BlocBuilder definition:
The name 'TState' isn't a type so it can't be used as a type argument.
Try correcting the name to an existing type, or defining a type named 'TState'.
I am not sure if this is an intentional design choice, or a bug that no one noticed, but i sure hope this gets fixed.
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
leafpetersen commentedon Oct 11, 2019
I'm really not sure what you're asking for here. Are you suggesting that given an example like:
in the case that
TState
is not an already declared identifier in scope, we should treat otherwise unbound free variables as being implicitly part of the parameter list? What if there are multiple ones? How do we order them (and how does the user see what order was chosen)? In general, this seems like something that's going to be extremely fragile and surprising to users. Am I misunderstanding what you are asking?bigworld12 commentedon Oct 11, 2019
i think a simpler way to express this is to make the
extends
clause able to define type parameters, not only testing against them.or another way of solving this is to keep
as it is, but make this a valid definition by inferring TState
leafpetersen commentedon Oct 11, 2019
Ok, leaving aside the question of implicitly defining type parameters which I think is problematic, the question of inferring missing type arguments seems more reasonable. I think you're proposing that if type arguments are left off (as in
BlockBuilder<TestBloc>
) that we solve for the missing type arguments based on the ones provided. This is probably more technically feasible. My initial reaction is that I'd want some kind of explicit syntax to indicate to the reader of the code that there were missing arguments there, e.g.BlockBuilder<TestBloc, _>
, but perhaps there's a good argument for allowing missing trailing arguments implicitly since it could permit adding type parameters to be a non-breaking change in some situations.Without explicit per variable syntax for the elided variables, we'd need to restrict this to trailing arguments. Otherwise you don't know which parameters to match up the arguments provided against.
bigworld12 commentedon Oct 11, 2019
I am not sure having to explicitly state a place holder for inferred types is necessary, the compiler can just check if the type argument has been assigned before to infer it.
the checking part already happens though, e. g.
will give a compile-time error, since int is not of type String.
leafpetersen commentedon Oct 11, 2019
It is necessary if you want to allow arguments other than the trailing arguments to be omitted.
bigworld12 commentedon Oct 11, 2019
do you mean this case ?
this makes sense, since the compiler isn't sure whether you want to check
TestSomethingElse
for extendingTEvent
, or if it's an implicitly defined argument,so i think we have to allow only trailing arguments to be omitted
bigworld12 commentedon Oct 11, 2019
we can even add some modifier at the declaration to specify the type as implicit, this way we don't have to care where is it located, e.g.
implicit
modifiermaking this a valid declaration :
leafpetersen commentedon Oct 11, 2019
Thinking about this some more, I wonder if this isn't actually more of a request for patterns rather than inference. That is, something more like (inventing some syntax):
cc @munificent I wonder if this is another pattern matching use case to consider? I do feel like I've seen this style of code before, where a type variable exists just to give a name to a part of another type variable.
bigworld12 commentedon Oct 17, 2019
is there any update on this ? I think @leafpetersen 's proposal is pretty good
eernstg commentedon Oct 18, 2019
We could use type patterns for this, cf. #170:
This would then perform static type pattern matching only (there is no need for a match at run time, because the actual type arguments will be denotable types at instance creation, and the construct would be desugared to have two type arguments).
The type parameters that are introduced by matching would have bounds derived from the context of the pattern (so
TState
would have the bound required to allow it to be passed toBloc
). It is possible that this would give rise to some non-trivial equation solving tasks, so that's definitely one thing to look out for (and possibly use to reject some patterns because they're intractable).There are two perspectives on this, and I think that both amount to a useful feature:
<TBloc extends Bloc<dynamic, TState>, TState>
, except that the parameterized types in client land can be more concise.BlocBuilder
to decomposeTBloc
, which would be a useful feature anyway (and which could be provided as a new form of<type>
using type patterns), but this particular approach might be extra convenient because it's so concise.So this would actually be quite nice!
rrousselGit commentedon Jan 25, 2020
For now, we can make a custom method to partially solve this issue:
Which can then be used this way:
This is not ideal, but unblock the situations where we have control over the base class.
On the other hand, this issue is still important for classes where we don't have the ability to modify the implementation class, such as:
It's also important for situations where we want that generic parameter to be the type of the result of a function:
felangel commentedon Jan 27, 2020
@rrousselGit thanks for the suggestion but unless I'm misunderstanding I don't think this workaround applies when you have a generic parameter which extends a class that has another generic parameter.
In the above example, I can't seem to force Dart to resolve the type of
S
in classBar
. It correctly resolvesT
in classBar
asFoo<int>
but withinBar
the functionbaz
still interpretsS
asdynamic
unless you explicitly specify the types32 remaining items