问题
I have the following, which works for ints:
extension IterableInt on Iterable<int> {
int get max => reduce(math.max);
int get min => reduce(math.min);
int get sum => reduce((a, b) => a + b);
}
I wanted to make this more general to include decimals and created this:
extension IterableNum on Iterable<num> {
num get max => reduce(math.max);
num get min => reduce(math.min);
num get sum => reduce((a, b) => a + b);
}
These mostly fail, using the following tests:
void main(List<String> args) {
print([1.2, 1.3, 5, 2.2].sum); //works
print([1.2, 1.3, 5, 2.2].max); //works
print([1.2, 1.3, 5, 2.2].min); //works
print([1.2, 1.3, 5.0, 2.2].sum); //does not work
print([1, 2, 3].max); //does not work
print([1, 2, 3].min); //does not work
print([1, 2, 3].sum); //does not work
}
The error returned for the decimal case is:
type '(num, num) => num' is not a subtype of type '(double, double) => double' of 'combine'
It's similar for int, referring to int instead of double in the message.
The pattern seems to be that if there is a mixture of ints and decimals in the list then it's fine. If it's all ints or all decimals, it fails.
I've redefined the properties (and added multiply) using fold and this works:
extension IterableNum on Iterable<num> {
num get max => fold(first, math.max);
num get min => fold(first, math.min);
num get sum => fold(0, (a, b) => a + b);
num get multiply => fold(1, (a, b) => a * b);
}
Can someone explain why the first version of IterableNum (using reduce) does not work?
回答1:
It does not work because you create lists of higher levels (subclasses of num type).
Your (extension) code should be more generic (more universal).
Like this code: extension IterableNum<T extends num> on Iterable<T>.
void main(List<String> args) {
print([1.2, 1.3, 5, 2.2].runtimeType);
print([1.2, 1.3, 5.0, 2.2].runtimeType);
}
Result:
List<num>
List<double>
Correct code as follow:
import 'dart:math' as math;
void main(List<String> args) {
print([1.2, 1.3, 5, 2.2].runtimeType);
print([1.2, 1.3, 5.0, 2.2].runtimeType);
print([1.2, 1.3, 5, 2.2].sum); //works
print([1.2, 1.3, 5, 2.2].max); //works
print([1.2, 1.3, 5, 2.2].min); //works
print([1.2, 1.3, 5.0, 2.2].sum); // WORKS!!!
print([1, 2, 3].max); // WORKS!!!
print([1, 2, 3].min); // WORKS!!!
print([1, 2, 3].sum); // WORKS!!!
}
extension IterableNum<T extends num> on Iterable<T> {
T get max => reduce(math.max);
T get min => reduce(math.min);
T get sum => reduce((a, b) => a + b as T);
}
Result:
List<num>
List<double>
9.7
5
1.2
9.7
3
1
6
回答2:
The issue is that reduce is very picky about its parameter function.
A List<int>'s reduce requires a function of type int Function(int, int). Nothing else will suffice. If you cast this List<int> to List<num>, you have something which seems like it expects to get a num Function(num, num), but actually requires an int Function(int, int). That's very hard to satisfy both of these (you need an int Function(num, num), which (a, b) => a + b is not.
(This is all because Dart class generics is unsafely covariant. A List<int> is always considered a subtype of List<num>, even though some of the functions aren't actually usable at List<num>).
Use fold is a good solution. It allows you to specify a return type different from the element type. It also requires you to have a value of that type to begin with, which is why it isn't always possible to use fold instead of reduce.
来源:https://stackoverflow.com/questions/65739652/creating-sum-min-max-properties-on-iterators-in-dart