带重复参数的磁铁模式(可变参数)

是否可以使用可变参数的磁铁模式:

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"


正如gourlaysama已经提到的那样,把可变参数变成一个单一的Product将会做到这一点,从句法上来说:

implicit def fromInts(t: Product) = Values()

这使得以下调用可以很好地编译:

Foo.bar(1,2,3)

这是因为编译器自动将3个参数提升为Tuple3[Int, Int, Int] 。 这将与任何数量的参数一起工作到22位。现在的问题是如何使它安全。 因为它是Product.productIterator是返回方法体内的参数列表的唯一方法,但它返回一个Iterator[Any] 。 我们不保证该方法只能用Int来调用。 这应该不会让人吃惊,因为我们实际上从未在签名中甚至没有提到我们只需要Int

好的,所以无约束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来取回我们的参数列表。

请注意,如果我们尝试使用不是Int的元组的元组来调用bar ,我们将得到一个编译错误,这会让我们返回类型安全。


更新 :正如0__所指出的,我的上述解决方案在数字展宽方面效果不佳。 换句话说,下面的代码不会编译,尽管如果bar只是简单地取3个Int参数:

Foo.bar(1:Short,2:Short,3:Short)
Foo.bar(1:Short,2:Byte,3:Int)

为了解决这个问题,我们所需要做的就是修改IsVarArgsOf以便所有IsVarArgsOf允许元组elemts可以转换为通用类型,而不是全部属于同一类型:

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")
                 ^

编辑:

隐含的var-args永远不会被选中,因为重复的参数在类型方面并不是真正的第一类公民......它们只在检查方法对参数的适用性时才存在。

所以基本上,当你调用Foo.bar(1,2,3)它会检查bar是否由变量参数定义,并且因为它不是,所以它不适用于参数。 它不能再进一步:

如果你用一个参数调用它,它会寻找从参数类型到期望类型的隐式转换,但是由于你用多个参数调用,存在一个参数问题,它不可能将多个参数到一个隐式类型转换。


但是:有一个使用自动tupling的解决方案。

Foo.bar(1,2,3)

可以被编译器理解为

Foo.bar((1,2,3))

这意味着像这样一个隐含的工作:

implicit def fromInts[T <: Product](t: T) = Values()
// or simply
implicit def fromInts(t: Product) = Values()

问题在于获取参数的唯一方法是通过t.productIterator返回一个Iterator[Any]并需要进行转换。

所以你会失去类型安全; 这会编译(并在使用时在运行时失败):

Foo.bar("1", "2", "3")

我们可以使用Scala 2.10的隐式宏来完全保证类型安全。 宏只会检查参数是否确实是一个TupleX[Int, Int, ...]并且只有在它通过该检查时才能将其作为隐式转换使用。

为了让这个例子更加有用,我改变了Values以保持Int参数:

case class Values(xs: Seq[Int])

object Values {
  implicit def fromInt (x : Int ) = Values(Seq(x))
  implicit def fromInts[T<: Product](t: T): Values = macro Macro.fromInts_impl[T]
}

随着宏的实施:

import scala.language.experimental.macros
import scala.reflect.macros.Context
object Macro {
  def fromInts_impl[T <: Product: c.WeakTypeTag](c: Context)(t: c.Expr[T]) = {
    import c.universe._

    val tpe = weakTypeOf[T];

    // abort if not a tuple
    if (!tpe.typeSymbol.fullName.startsWith("scala.Tuple"))
      c.abort(c.enclosingPosition, "Not a tuple!")

    // extract type parameters
    val TypeRef(_,_, tps) = tpe

    // abort it not a tuple of ints
    if (tps.exists(t => !(t =:= typeOf[Int])))
      c.abort(c.enclosingPosition, "Only accept tuples of Int!")

    // now, let's convert that tuple to a List[Any] and add a cast, with splice
    val param = reify(t.splice.productIterator.toList.asInstanceOf[List[Int]])

    // and return Values(param)
    c.Expr(Apply(Select(Ident(newTermName("Values")), newTermName("apply")),
      List(param.tree)))
  }
}

最后,像这样定义Foo

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

您可以像使用重复参数一样使用语法来获得类型安全的调用:

scala> Foo.bar(1,2,3)
Values(List(1, 2, 3))

scala> Foo.bar("1","2","3")
<console>:13: error: too many arguments for method bar: (values: Values)Unit
              Foo.bar("1","2","3")
                     ^

scala> Foo.bar(1)
Values(List(1))

该规范仅指定函数内部重复参数的类型(可变参数):

该方法内部的这种重复参数的类型是序列类型scala.Seq [T]。

它不包括其他地方的类型。

所以我认为编译器内部 - 在某个阶段 - 不能匹配类型。

从这个观察(这不会编译=>“双重定义”):

object Values {
  implicit def fromInt(x: Int) = Values()
  implicit def fromInts(xs: Int*) = Values()
  implicit def fromInts(xs: Seq[Int]) = Values()
}

它似乎是Seq []。 所以下一个尝试就是让它与众不同:

object Values {
  implicit def fromInt(x: Int) = Values()
  implicit def fromInts(xs: Int*) = Values()
  implicit def fromInts(xs: Seq[Int])(implicit d: DummyImplicit) = Values()
}

这编译,但这并没有解决真正的问题。

我发现的唯一解决方法是将可变参数显式转换为一个序列:

def varargs(xs: Int*) = xs // return type is Seq[Int]

Foo.bar(varargs(1, 2, 3))

但这当然不是我们想要的。

可能相关:隐式转换函数只有一个参数。 但从逻辑(或编译器的临时)角度来看,如果是可变参数,它可能也是多个。

至于类型,这可能是有趣的

链接地址: http://www.djcxy.com/p/13841.html

上一篇: Magnet pattern with repeated parameters (varargs)

下一篇: C++11 class lattice with mixed virtual and non