Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't Dart infer the type of List.fold()?

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.

like image 493
Nick Lee Avatar asked Jan 26 '19 14:01

Nick Lee


1 Answers

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.

Is it this a Dart limitation?

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:

  1. fold returned value is assigned to an Object obj, 0 is an Object since it is an int, then T "resolve to" Object

  2. 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);

}
like image 187
attdona Avatar answered Sep 30 '22 05:09

attdona