我遵循我在标准库的实现中看到的内容。
这个想法是我们有一个干净的类型化接口和一个高效的该接口的运行时实现。
所以,首先,我需要对ZipAll
类型内部的信息进行编码:
type ZipAll [L <: Tuple, R <: Tuple, TL, TR] <: Tuple = (L, R) match {
case (hl *: restl, hr *: restr) => (hl, hr) *: ZipAll[restl, restr, TL, TR]
case (EmptyTuple, h *: rest) => (TL, h) *: ZipAll[EmptyTuple, rest, TL, TR]
case (h *: rest, EmptyTuple) => (h, TR) *: ZipAll[rest, EmptyTuple, TL, TR]
case (EmptyTuple, EmptyTuple) => EmptyTuple
}
这种特殊类型确保当我们使用 时ZipAll
,相应的结果类型将是正确的。特别是,ZipAll
使用标准类型 TL(如果左侧元组比右侧元组短)或 TR(反之亦然)创建一个填充缺失元素的元组。因此,所有下一个类型测试都是正确的:
summon[ZipAll[(Int, Int), (Int, Double, Int), String, Int] =:= ((Int, Int), (Int, Double), (String, Int))
summon[ZipAll[(Int, Double, Int), (Int, Int), String, Int] =:= ((Int, Int), ( Double, Int), (Int), Int)
现在,我们可以创建实现的类型签名(使用扩展方法):
extension[L <: Tuple](t: L) {
def zipAll[R <: Tuple, TR, TL](
r: R, endL: TL, endR: TR
): ZipAll[L, R, TL, TR] = ???
}
现在,我们可以创建一个不安全的实现zipAll
:
def runtimeZipAll(l: Tuple, r: Tuple, endL: Any, endR: Any): Tuple = (l, r) match {
case (hl *: restl, hr *: restr) => (hl, hr) *: zipAll(restl, restr, endL, endR)
case (EmptyTuple, hr *: restr) => (endL, hr) *: zipAll(EmptyTuple, restr, endL, endR)
case (hl *: restl, EmptyTuple) => (hl, endR) *: zipAll(restl, EmptyTuple, endL, endR)
case (EmptyTuple, EmptyTuple) => EmptyTuple
}
这应该隐藏在实现中。然后,我们可以使用以下命令强制执行该不安全实现的正确类型asInstanceOf...
:
def zipAll[R <: Tuple, TR, TL](r: R, endL: TL, endR: TR): ZipAll[L, R, TL, TR] =
runtimeZipAll(l, r, endL, endR).asInstanceOf[ZipAll[L, R, TL, TR]]
然后,我应该无缝地使用新功能:
val res: ((Int, Int), (Int, Int), (String, Int)) = (1, 2).zipAll((10, 20, 30), "hello", 20.0) // should be correctly typed
可能它不是迄今为止最好的实现(实际上内部细节不是类型安全的),但它是一种实现安全接口和不安全实现的模式(在类型安全和性能之间取得良好的平衡)。
这里是我迄今为止在 Scastie 中描述的内容的实现。