Dart中为Iterable实现sum/min/max属性时reduce报错的原因分析
第一版
reduce实现失败的核心原因 reduce方法的严格类型约束特性Iterable<E>.reduce的方法签名为E reduce(E combine(E value, E element)),要求传入的聚合函数必须严格匹配(E, E) => E的类型,其中E是当前Iterable实例的实际元素类型。
当你在Iterable<num>的扩展方法内编写reduce(math.max)时,静态分析会基于扩展的接收者类型Iterable<num>,将该调用的泛型E推断为num,此时传入的聚合函数会被特化为(num, num) => num类型(math.max是泛型方法,sum的匿名函数参数和返回值也都是num类型)。
最初针对Iterable<int>编写的扩展可以正常运行,就是因为在那个扩展的静态上下文里,reduce的泛型E就是int,传入的聚合函数会被特化为(int, int) => int,和List<int>自身的reduce要求完全匹配,不存在类型偏差。- Dart字面量列表的类型推断规则
对纯数值类型的列表字面量,Dart会优先推断为最具体的子类型:- 全是int元素的
[1,2,3],静态类型为List<int>,对应E = int - 全是double元素的
[1.2, 1.3, 5.0, 2.2],静态类型为List<double>,对应E = double - 混合int和double的列表,因为不存在同时兼容两种类型的具体子类型,会被推断为
List<num>,对应E = num
- 全是int元素的
- 类型不匹配的触发逻辑
当调用纯int/纯double列表的扩展方法时,虽然代码层面是从Iterable<num>的视角发起的调用,但运行时实际执行的是具体List<int>/List<double>自带的reduce实现,它会强制校验传入的聚合函数,必须完全符合(int, int) => int/(double, double) => double的签名要求。
而你传入的聚合函数,是在扩展方法的静态上下文里特化后的(num, num) => num类型:- 这个函数的入参要求是能接受任意num类型,看似可以兼容int/double入参,但从严格类型规则上,接受宽泛num类型参数的函数,不能替代要求接受具体int/double参数的函数
- 同时这个函数的返回值是宽泛的num类型,没有保证返回值一定是int/double,也不满足要求
最终就会触发运行时的类型校验错误,和你观察到的报错完全一致。
而混合int和double的列表,静态类型本身就是List<num>,调用reduce时的类型要求和传入的聚合函数完全匹配,因此可以正常运行。
fold方法可以正常工作的原因Iterable<E>.fold的方法签名为Z fold<Z>(Z initialValue, Z combine(Z previousValue, E element)),泛型Z由传入的初始值类型决定:
在扩展方法内编写fold(first, math.max)时,初始值first的静态类型是num,因此泛型Z被推断为num,聚合函数的类型要求为(num, num) => num,和传入的math.max、加法/乘法匿名函数的类型完全匹配。
运行时不会要求聚合函数匹配int/double的具体子类型:因为Z是更宽泛的num类型,实际遍历过程中把int/double元素传给接受num类型的聚合函数是完全合法的,不存在严格的子类型校验,因此可以正常运行。
内容的提问来源于stack exchange,提问作者Praful
相关产品推荐
相关产品推荐

