具有重复参数的磁铁模式(varargs)

0__*_*0__ 7 scala variadic-functions implicit-conversion

是否可以使用带有varargs 的磁铁模式:

object Values {
  implicit def fromInt (x : Int ) = Values()
  implicit def fromInts(xs: Int*) = Values()
}
case class Values()

object Foo {  
  def bar(values: Values) {}
}

Foo.bar(0)
Foo.bar(1,2,3) // ! "error: too many arguments for method bar: (values: Values)Unit"
Run Code Online (Sandbox Code Playgroud)

Rég*_*les 3

正如 gourlaysama 已经提到的,从语法上来说,将可变参数变成单个可变参数Product就可以了:

implicit def fromInts(t: Product) = Values()
Run Code Online (Sandbox Code Playgroud)

这使得以下调用可以正常编译:

Foo.bar(1,2,3)
Run Code Online (Sandbox Code Playgroud)

这是因为编译器自动将 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)
Run Code Online (Sandbox Code Playgroud)

我们改变了方法签名形式

implicit def fromInts(t: Product)
Run Code Online (Sandbox Code Playgroud)

到:

implicit def fromInts[P]( xs: P )( implicit evidence: P IsVarArgsOf Int )
Run Code Online (Sandbox Code Playgroud)

在方法体内,我们使用新的 methoddargs来获取我们的 arg 列表。

请注意,如果我们尝试bar使用不是Ints 元组的元组进行调用,我们将得到一个编译错误,这使我们恢复了类型安全。


更新:正如 0__ 所指出的,我的上述解决方案在数字加宽方面效果不佳。换句话说,下面的代码无法编译,尽管如果bar只采用 3 个Int参数就可以工作:

Foo.bar(1:Short,2:Short,3:Short)
Foo.bar(1:Short,2:Byte,3:Int)
Run Code Online (Sandbox Code Playgroud)

要解决此问题,我们需要做的就是进行修改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 ...
}
Run Code Online (Sandbox Code Playgroud)

好吧,实际上我撒谎了,我们还没有结束。因为我们现在接受不同类型的元素(只要它们可以转换为通用类型,我们就不能将它们强制转换为预期类型(这会导致运行时强制转换错误),而是必须应用隐式转换。我们可以这样修改它:

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 )
  }
}
Run Code Online (Sandbox Code Playgroud)

这解决了数字加宽的问题,并且在混合不相关类型时我们仍然得到编译:

scala> Foo.bar(1,2,"three")
<console>:22: error: too many arguments for method bar: (values: Values)Unit
          Foo.bar(1,2,"three")
                 ^
Run Code Online (Sandbox Code Playgroud)