正如 gourlaysama 已经提到的,从语法上讲,将可变参数转换为单个 Product 就可以了:
implicit def fromInts(t: Product) = Values()
这允许以下调用编译正常:
Foo.bar(1,2,3)
这是因为编译器自动将 3 个参数提升为 Tuple3[Int, Int, Int]。这将适用于最多 22 个参数的任意数量的参数。现在的问题是如何使其类型安全。因为Product.productIterator 是在方法体中取回我们的参数列表的唯一方法,但它返回一个Iterator[Any]。我们不保证仅使用Ints 调用该方法。这应该不足为奇,因为我们实际上甚至从未在签名中提到我们只想要Ints。
好的,因此不受约束的Product 和可变参数列表之间的主要区别在于,在后一种情况下,每个元素都属于同一类型。我们可以使用类型类对其进行编码:
abstract sealed class IsVarArgsOf[P, E]
object IsVarArgsOf {
implicit def Tuple2[E]: IsVarArgsOf[(E, E), E] = null
implicit def Tuple3[E]: IsVarArgsOf[(E, E, E), E] = null
implicit def Tuple4[E]: IsVarArgsOf[(E, E, E, E), E] = null
implicit def Tuple5[E]: IsVarArgsOf[(E, E, E, E, E), E] = null
implicit def Tuple6[E]: IsVarArgsOf[(E, E, E, E, E), E] = null
// ... and so on... yes this is verbose, but can be done once for all
}
implicit class RichProduct[P]( val product: P ) {
def args[E]( implicit evidence: P IsVarArgsOf E ): Iterator[E] = {
// NOTE: by construction, those casts are safe and cannot fail
product.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[E]]
}
}
case class Values( xs: Seq[Int] )
object Values {
implicit def fromInt( x : Int ) = Values( Seq( x ) )
implicit def fromInts[P]( xs: P )( implicit evidence: P IsVarArgsOf Int ) = Values( xs.args.toSeq )
}
object Foo {
def bar(values: Values) {}
}
Foo.bar(0)
Foo.bar(1,2,3)
我们改变了方法签名形式
implicit def fromInts(t: Product)
到:
implicit def fromInts[P]( xs: P )( implicit evidence: P IsVarArgsOf Int )
在方法体内,我们使用新的方法args 来获取我们的参数列表。
请注意,如果我们尝试使用不是 Ints 的元组的元组调用 bar,我们将收到编译错误,这会恢复我们的类型安全性。
更新:正如 0__ 所指出的,我的上述解决方案不能很好地用于数值扩展。换句话说,以下内容不会编译,尽管如果 bar 只是采用 3 个 Int 参数,它会起作用:
Foo.bar(1:Short,2:Short,3:Short)
Foo.bar(1:Short,2:Byte,3:Int)
要解决这个问题,我们需要做的就是修改 IsVarArgsOf 以便所有隐式都允许
元组元素可转换为通用类型,而不是全部为同一类型:
abstract sealed class IsVarArgsOf[P, E]
object IsVarArgsOf {
implicit def Tuple2[E,X1<%E,X2<%E]: IsVarArgsOf[(X1, X2), E] = null
implicit def Tuple3[E,X1<%E,X2<%E,X3<%E]: IsVarArgsOf[(X1, X2, X3), E] = null
implicit def Tuple4[E,X1<%E,X2<%E,X3<%E,X4<%E]: IsVarArgsOf[(X1, X2, X3, X4), E] = null
// ... and so on ...
}
好吧,其实我撒谎了,我们还没有完成。因为我们现在接受不同类型的元素(只要它们可以转换为通用类型,我们就不能将它们转换为预期的类型(这会导致运行时转换错误),而是必须应用隐式转换。我们可以这样修改它:
abstract sealed class IsVarArgsOf[P, E] {
def args( p: P ): Iterator[E]
}; object IsVarArgsOf {
implicit def Tuple2[E,X1<%E,X2<%E] = new IsVarArgsOf[(X1, X2), E]{
def args( p: (X1, X2) ) = Iterator[E](p._1, p._2)
}
implicit def Tuple3[E,X1<%E,X2<%E,X3<%E] = new IsVarArgsOf[(X1, X2, X3), E]{
def args( p: (X1, X2, X3) ) = Iterator[E](p._1, p._2, p._3)
}
implicit def Tuple4[E,X1<%E,X2<%E,X3<%E,X4<%E] = new IsVarArgsOf[(X1, X2, X3, X4), E]{
def args( p: (X1, X2, X3, X4) ) = Iterator[E](p._1, p._2, p._3, p._4)
}
// ... and so on ...
}
implicit class RichProduct[P]( val product: P ) {
def args[E]( implicit isVarArg: P IsVarArgsOf E ): Iterator[E] = {
isVarArg.args( product )
}
}
这解决了数值扩大的问题,当混合不相关的类型时我们仍然可以编译:
scala> Foo.bar(1,2,"three")
<console>:22: error: too many arguments for method bar: (values: Values)Unit
Foo.bar(1,2,"three")
^