Scala 3如何单方法获取支持Iterable与Array的given Factory
问题背景
最初定义的排序逻辑特质如下:
trait Sorting: def sort[A, B >: A, C <: IterableOnce[A]](list: C)(using ord: Ordering[B], factory: Factory[A, C]): C
对应插入排序实现:
object InsertionSorting extends Sorting: override def sort[A, B >: A, C <: IterableOnce[A]](list: C)(using ord: Ordering[B], factory: Factory[A, C]): C = list.iterator .foldLeft(ListBuffer.empty[A]) { (acc, x) => { val index = acc.view.takeWhile(y => ord.lt(y, x)).size acc.insert(index, x) acc } } .to(factory)
针对Seq类型编写测试用例时可正常运行:
class InsertionSortingSpec extends AnyFlatSpec with should.Matchers: "A Sorting" should "sort correctly" in { val sorting: Sorting = InsertionSorting val input = Seq(31, 41, 59, 26, 41, 58) val actual = sorting.sort(input) actual shouldBe sorted }
但将输入替换为Array类型时编译失败,报错信息如下:
[error] 11 | val actual = sorting.sort(input) [error] | ^ [error] |no implicit argument of type collection.Factory[A, scala.collection.mutable.ArraySeq.ofInt] was found for parameter factory of method sort in trait Sorting [error] | [error] |where: A is a type variable with constraint >: Int
最初的临时解决方案是给特质额外增加Array专属的重载方法:
trait Sorting: def sort[A, B >: A, C <: IterableOnce[A]](list: C)(using ord: Ordering[B], factory: Factory[A, C]): C def sort[A: ClassTag, B >: A](array: Array[A])(using Ordering[B]): Array[A] = sort(array.iterator).toArray[A]
待解决的两个核心问题:
- 是否存在方案可以仅用一个方法同时兼容Iterable系列集合与Array类型?
- 为什么IterableOnce的
to方法没有将factory参数设置为using上下文参数?方法签名如下:def to[C1](/*using*/ factory: Factory[A, C1]): C1
解决方案
单方法兼容Iterable与Array的实现
报错的核心原因是:Array本身并不是IterableOnce的子类型,Scala集合库是通过隐式转换将Array包装为ArraySeq来适配Iterable接口的。原方法给泛型C加了C <: IterableOnce[A]的上界约束,传入Array时会提前触发隐式转换,把输入类型替换为ArraySeq.ofInt,此时编译器不会再寻找返回Array所需的Factory,反而去寻找不存在的Factory[Int, ArraySeq.ofInt],自然编译失败。
只需要调整泛型约束,去掉C的上界,通过上下文参数提供类型转换能力即可单方法兼容两类集合,最终实现如下:
object InsertionSorting extends Sorting: override def sort[A, B >: A: Ordering, C]( xs: C )(using view: C => IterableOnce[A], factory: Factory[A, C])(using ClassTag[A]): C = import math.Ordered.orderingToOrdered view(xs).iterator .foldLeft(ListBuffer.empty[A]) { (acc, x) => { val index = acc.view.takeWhile(_ < x).size acc.insert(index, x) acc } } .to(factory)
实现逻辑说明:
- 去掉
C <: IterableOnce[A]的硬约束,允许直接传入Array作为C类型参数,不会提前触发隐式转换抹掉原始Array类型 - 增加
view: C => IterableOnce[A]上下文参数,无论是Iterable子类的子类型关系,还是Array到IterableOnce的隐式转换,都能满足该参数要求,统一拿到迭代器执行排序逻辑 - 增加
ClassTag[A]上下文参数,满足Array构建时的类型标记要求;普通Iterable集合调用时,编译器能自动解析到兼容的given实例,不会影响原有调用逻辑
IterableOnce.to方法不使用using参数的原因
这个是集合库的刻意设计,核心考量有三点:
- 避免隐式解析歧义:如果把factory设为上下文参数,调用时编译器会自动在作用域内搜索可用的Factory实例,一旦作用域内存在多个同类型工厂实例,很容易产生隐式冲突,选中非预期的实现
- 符合方法语义:
to本身是显式的集合转换操作,调用方本来就需要明确指定转换的目标类型,显式传入工厂参数比隐式解析更符合"主动指定转换目标"的语义;而Ordering这类上下文参数属于排序逻辑的策略依赖,对调用方是透明的,更适合用上下文参数传递 - 调用成本已经足够低:Scala 2.13+的集合库为所有集合伴生对象提供了Factory实例,调用时只需要写
list.to(List)、list.to(Array)即可,不需要额外手动构造工厂,没有必要再做成上下文参数增加隐式解析的复杂度。
内容的提问来源于stack exchange,提问作者iron9light
相关产品推荐
相关产品推荐

