问题
I have the following, which works for int
s:
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 int
s and decimal
s in the list then it's fine. If it's all int
s or all decimal
s, 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