正如 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)
to:
implicit def fromInts[P]( xs: P )( implicit evidence: P IsVarArgsOf Int )
在方法体内,我们使用新方法args
获取我们的 arg 列表。
请注意,如果我们尝试调用bar
与一个不是元组的元组Int
s,我们将得到一个编译错误,这使我们恢复了类型安全。
UPDATE:正如 0__ 所指出的,我的上述解决方案不能很好地适应数字加宽。换句话说,以下内容无法编译,尽管如果bar
只需要 3Int
参数:
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")
^