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

2,467

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

}
Share:
2,467
Nick Lee
Author by

Nick Lee

Updated on December 09, 2022

Comments

  • Nick Lee
    Nick Lee over 1 year

    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.

  • Nick Lee
    Nick Lee about 5 years
    Well, I am still a little disappointed. Shouldn't it recognize that int is a subclass of Object and, given the "context", int is a more appropriate type to infer? I see what you are saying, but I still think Dart's type inference could be better.
  • attdona
    attdona about 5 years
    It make sense your reasoning. I've updated with some considerations, I hope they are helpful.
  • Nick Lee
    Nick Lee about 5 years
    Your spec for fold is a little off. Fold's spec should be T fold <T>(T initialValue, T combine(T previousValue, E element)). Note that the element type can be different. Other than that, I don't quite see what you are getting at, although I admit there may be theoretical concerns which I am not aware of.
  • attdona
    attdona about 5 years
    Yes, for the sake of thid post I preferred to simplify about the spec of fold, because I think it is wrong in the docs: for what I understand it should be T fold <T, E>(T initialValue, T combine(T previousValue, E element)).
  • IL_Agent
    IL_Agent over 3 years
    >Dart type inference works as expected: To be honest in your first example inference doesn't work because the type of a is dynamic actually when 'int' is expected. It can be fixed by type specifying int a. But anyway this is not the behavior that is expected .