I am trying List.fold()
in Dart 2.1.0:
void main() {
final a = [1,2,3];
print(a.fold(0, (x,y) => x + y));
}
It comes back with an error:
Error: The method '+' isn't defined for the class 'dart.core::Object'.
Try correcting the name to the name of an existing method, or defining a method named '+'.
Seems that it can't infer the type of x
to be int
, so doesn't know how to apply the +
there.
According to the method spec, x
should have the same type as the initial value 0
, which is obviously an int
. Why can't Dart infer that?
I can make it work by explicitly tipping the type:
void main() {
final a = [1,2,3];
print(a.fold<int>(0, (x,y) => x + y));
}
But I am a little disappointed that Dart cannot infer that for me. Its type inference seems stronger in most other cases.
Dart type inference works as expected:
void main() {
final a = [1,2,3];
var sum = a.fold(0, (x,y) => x + y);
print(sum);
}
The reason it fails in your example is because type inference outcome depends on the context where the expression is evaluated:
print(a.fold(0, (x,y) => x + y));
throws an error because print
parameter is expected to be an Object
: inference uses Object
to fill generic T
type argument` of fold:
T fold <T>(T initialValue, T combine(T previousValue, T element)) :
To validate this assumption:
void main() {
final a = [1,2,3];
Object obj = a.fold(0, (x,y) => x + y);
}
Throws exactly the same error.
Take away:
Type inference works well, but care must be paid to the generic expression's surrounding context.
I dont think this behavoir it is a limitation of dart, just an implementation choice.
Probably there are also sound theoretical reasons that I'm not able to speak about but I can works out some reasoning about that.
Considering the generic method:
T fold <T>(T initialValue, T combine(T previousValue, T element))
and its usage:
Object obj = a.fold(0, (x,y) => x + y);
There are two paths for inferring the type of T
:
fold
returned value is assigned to an Object obj
, 0
is an Object
since it is an int
, then T
"resolve to" Object
fold
first argument is an int
, the returned value is expected to be an Object
and int
is an Object
,
then T
"resolve to" int
Dart chooses the path 1: it takes for inferred type the "broadest" (the supertype) that satisfy the generic method.
Should be better if Dart implements path 2 (the inferred type is the "nearest" type)?
In this specific case probably yes, but then there will be cases that does not works with path 2.
For example this snippet would not be happy with path 2:
abstract class Sensor {
String getType();
}
class CADPrototype extends Sensor {
String getType() {
return "Virtual";
}
}
class Accelerometer extends Sensor {
String getType() {
return "Real";
}
}
T foo<T extends Sensor>(T v1, T v2, T bar(T t1, T t2)) {
if (v2 is CADPrototype) {
return v1;
}
return v2;
}
Sensor foo_what_dart_does(Sensor v1, Sensor v2, Sensor bar(Sensor t1, Sensor t2)) {
if (v2 is CADPrototype) {
return v1;
}
return v2;
}
Accelerometer foo_infer_from_argument(Accelerometer v1, Accelerometer v2, Accelerometer bar(Accelerometer t1, Accelerometer t2)) {
if (v2 is CADPrototype) {
return v1;
}
return v2;
}
void main() {
Accelerometer v1 = Accelerometer();
CADPrototype v2 = CADPrototype();
Sensor result;
result = foo(v1, v2, (p1, p2) => p1);
// it works
result = foo_what_dart_does(v1, v2, (p1, p2) => p1);
// Compilation Error: CADPrototype cannot be assigned to type Accelerometer
result = foo_infer_from_argument(v1, v2, (p1, p2) => p1);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With