Home > Software engineering >  Dart firstWhere orElse: generic function return type
Dart firstWhere orElse: generic function return type

Time:03-01

The code below defines a generic myFirstWhereFunction with 3 arguments:

  • Generic list
  • Generic value to search in the list
  • Generic default value to return if the searched value is not in the passed generic list

The code:

void main() {
  const List<int> intLst = [1, 2, 3, 4];

  print(myFirstWhereFunc(intLst, 4, -1));
  print(myFirstWhereFunc(intLst, 5, -1));

  const List<String> strLst = ['coucou', 'go', 'bold', 'tooltip'];

  print(myFirstWhereFunc(strLst, 'go', 'not exist'));
  print(myFirstWhereFunc(strLst, 'ok', 'not exist'));
}

T myFirstWhereFunc<T>(List<T> lst, T searchVal, T defaultVal) {
  return lst.firstWhere((element) => element == searchVal, orElse: <T> () {
    return defaultVal;
  });
}

But this code generates an exception.

One solution is to replace the generic myFirstWhereFunc return type by dynamic (code below):

dynamic myFirstWhereFunc<T>(List<T> lst, T searchVal, T defaultVal) {
  return lst.firstWhere((element) => element == searchVal,
      orElse: () => defaultVal);
}

But is there another way of solving the problem ?

CodePudding user response:

You can pass in a type argument when calling the function:

void main() {
  const List<int> intLst = [1, 2, 3, 4];

  print(myFirstWhereFunc<int>(intLst, 4, -1));
  print(myFirstWhereFunc<int>(intLst, 5, -1));


  const List<String> strLst = ['coucou', 'go', 'bold', 'tooltip'];

  print(myFirstWhereFunc<String>(strLst, 'go', 'not exist'));
  print(myFirstWhereFunc<String>(strLst, 'ok', 'not exist'));
}

T myFirstWhereFunc<T>(List<T> lst, T searchVal, T defaultVal) {
  return lst.firstWhere((element) => element == searchVal,
      orElse: () => defaultVal);
}

Personally, I would consider refactoring your function to an extension method:

void main() {
  const List<int> intLst = [1, 2, 3, 4];

  print(intLst.myFirstWhereFunc(4, -1));
  print(intLst.myFirstWhereFunc(5, -1));

  const List<String> strLst = ['coucou', 'go', 'bold', 'tooltip'];

  print(strLst.myFirstWhereFunc('go', 'not exist'));
  print(strLst.myFirstWhereFunc('ok', 'not exist'));
}

extension MyFirstWhereExtension<T> on List<T> {
  T myFirstWhereFunc(T searchVal, T defaultVal) {
    return firstWhere((element) => element == searchVal,
        orElse: () => defaultVal);
  }
}

CodePudding user response:

I believe that the problem is that when you do:

print(myFirstWhereFunc(intLst, 4, -1));

there are two possible ways to infer type of myFirstWhereFunc:

  • Inside-out: myFirstWhereFunc is called with a List<int> and with int arguments, so its type could be myFirstWhereFunc<int>, which is what you want.
  • Outside-in: print has an Object? parameter, so myFirstWhereFunc could be myFirstWhereFunc<Object?> to return an Object?. This is what actually happens and is what you do not want.

Dart ends up with two possible ways to infer the generic type parameter, both seem equally valid at compilation-time, and it picks the one that you happen to not want. Picking the other approach would result in undesirable outcomes for different code examples. Arguably inference could try both ways and pick the narrower type if one approach leads to a subtype of the other. I'm not sure offhand if that would break code, but I wouldn't be surprised. (I also suspect that it's been suggested in https://github.com/dart-lang/language/issues somewhere...)

Changing myFirstWhereFunc's return type fixes the problem because it makes myFirsyWhereFunc's type no longer inferrable from print's parameter type.

Inference should do what you expect if you split the line up:

var intResult = myFirstWhereFunc(intLst, 4, -1);
print(intResult);
  • Related