I am working on an app using Dart and Flutter. The code below is a simplified and isolated version of the real problem I am running into.
I would like to make a list that holds only items of the generic class X. X has two generic values: Y and Z. I would like the elements in the list to have different values for Y and Z. So [X<Y1, Z1>, X<Y2, Z2>]
but I keep getting this exception:
"_TypeError (type ‘(DateFilter, Question) => bool’ is not a subtype of
type ‘(dynamic, dynamic) => bool’)"
I haven’t been able to figure out how to add extra generic type annotations to get it to work.
void main() {
List<Foo> list = [
Foo(arg: "hello", func: func1),
Foo(arg: 100, func: func2),
];
}
bool func1(Bar bar) {
return true;
}
bool func2(Bar bar) {
return false;
}
class Foo<T, U> {
final T arg;
final bool Function(U) func;
const Foo({
required this.arg,
required this.func
});
}
class Bar {
final String text;
const Bar({
required this.text
});
}
2
Answers
As mentioned by jamesdlin,
List<Foo>
is actuallyList<Foo<dynamic, dynamic>>
. Now that you already have a "contract" that the items oflist
will have theFoo<dynamic, dynamic>
type, you also make a "contract" that the function given to itsfunc
property will accept adynamic
as its parameter.Now here’s an example:
As you can see, calling
foo1.func(baz)
should be no problem, becausefoo1
is aFoo
and as we know we made contract the itsfunc
property will accept adynamic
as its parameter. But in fact, thefunc
that we passed to thefoo1
object is abool Function(Bar)
, which may containsBar
specific code that is not applicable to other types. That is why, the Dart analyzer has complained from the moment you passed abool Function(Bar)
to thefunc
parameter which is abool Function(dynamic)
.There is also a "consumer and producer" concept in substituting types, which defines when a subtype can be replaced by a supertype and when a supertype can be replaced by a subtype.
See: Substituting types
When you do:
List<Foo> list = [Foo(...)]
, you are declaringlist
to be of typeList<Foo>
, which is shorthand forList<Foo<dynamic, dynamic>>
. Inference then flows from left-to-right: the elements of the right-hand-side then are inferred to beFoo<dynamic, dynamic>
and therefore expectfunc
callbacks that takedynamic
arguments.If you instead omit the explicit declaration then the elements will be inferred to be
Foo<String, Bar>
andFoo<int, Bar>
, and inference will flow from right-to-left, causinglist
to be inferred to beList<Foo<Object, Bar>>
. (This is why omitting explicit type declarations can be safer. Also see https://stackoverflow.com/a/61807738/.)Regarding your comments about wanting to make
func1
andfunc2
take additionalT
arguments: you could makefunc1
andfunc2
generic onT
. For example:However, while it’s tempting to do only that (and that’s what I initially tried), it doesn’t quite do what’s intended. The inference flow doesn’t allow
func1<T>
to be inferred from the sibling argument toFoo
. (There’s some support for inferring argument types from sibling arguments, but there are limitations. See https://github.com/dart-lang/language/issues/731.)Explicitly specifying a type (e.g.
Foo('hello', func: func1<String>)
) is also problematic if you attempt to invoke the callback functions throughlist
. That is, something like:will fail at runtime:
list
isList<Foo<Object, Bar>>
,i
has a static type ofFoo<Object, Bar>
, and thereforei.func(bar, i.arg)
expects to invoke aFunction(Bar, Object)
but instead gets aFunction(Bar, String)
, which isn’t substitutable. (Furthermore,i.arg
is only statically known to be anObject
.)If you aren’t invoking the
Foo.func
methods directly throughlist
(which inherently loses static type information), then it’s not a problem. For example, you could do:which outputs:
showing that
func1
andfunc2
are properly typed.