正如 gourlaysama 已经提到的,将 varargs 变成一个单一的Product
就可以了,从语法上讲:
implicit def fromInts(t: Product) = Values()
这允许以下调用编译良好:
Foo.bar(1,2,3)
这是因为编译器自动将 3 个参数提升为Tuple3[Int, Int, Int]
. 这将适用于最多 22 个参数的任意数量的参数。现在的问题是如何使其类型安全。因为这是Product.productIterator
在方法体中取回参数列表的唯一方法,但它返回一个Iterator[Any]
. 我们不保证该方法将仅使用Int
s 调用。这应该不足为奇,因为我们实际上甚至从未在签名中提到我们只想要Int
s。
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
来获取我们的 arg 列表。
请注意,如果我们尝试bar
使用不是Int
s 的元组的元组进行调用,我们将得到一个编译错误,这使我们恢复了类型安全。
更新:正如 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")
^