【问题标题】:Magnet pattern with repeated parameters (varargs)具有重复参数的磁铁图案(可变参数)
【发布时间】:2013-06-02 02:19:24
【问题描述】:

是否可以将magnet pattern 与可变参数一起使用:

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"

?

【问题讨论】:

  • 不知道是不是理解正确:归结为这个问题:为什么1, 2, 3与第二个隐式转换函数的参数不匹配?
  • 确切地说,我想允许使用由Values 类型类捕获的可变数量的参数(不重载bar 并且不将可变参数放在bar 方法中)
  • 为了记录,Scala 的术语是“重复参数”,而不是“可变参数”。
  • @RandallSchulz 好点,java规范称它们为可变参数;我不确定 varargs 来自哪里...
  • @gourlaysama 好吧,他们有时称为varargs in Java

标签: scala implicit-conversion variadic-functions


【解决方案1】:

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

【讨论】:

  • 好的,我明白了。问题是由于隐含和数值扩展的困难,我实际上需要承认IntFloatDouble,所以隐含的数量会爆炸式增长。所以我的结论是这是不可能的。要么我删除重复的参数作为选项,要么我必须至少有一个重载方法(如我的回答中所述)。 :-/
  • 我会接受这个,因为它有效,但我真的更喜欢让我的课程更简单:)
  • 我可以理解 :-) 情况可能会更糟,毕竟一旦助手在实现站点编写语法开销相对较小,特别是在已经发生的混淆级别方面,因为的磁铁图案。
【解决方案2】:

编辑:

永远不会选择 var-args 隐式,因为重复参数在类型方面并不是真正的一等公民......它们仅在检查方法对参数的适用性时才会出现。

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

如果您使用单个参数调用它,它会寻找从参数类型到预期类型的​​隐式转换,但是由于您使用多个参数调用,所以存在数量问题,它无法使用隐式 type 转换将多个参数转换为单个参数。


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

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

【讨论】:

    【解决方案3】:

    规范只规定了函数内部中重复参数(varargs)的类型:

    方法内部这种重复参数的类型就是序列类型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))
    

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

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

    至于类型,this 可能会感兴趣

    【讨论】:

      【解决方案4】:

      这是一个确实使用重载的解决方案(我不想这样做)

      object Values {
        implicit def fromInt (x :     Int ) = Values()
        implicit def fromInts(xs: Seq[Int]) = Values()
      }
      case class Values()
      
      object Foo {  
        def bar(values: Values) { println("ok") }
        def bar[A](values: A*)(implicit asSeq: Seq[A] => Values) { bar(values: Values) }
      }
      
      Foo.bar(0)
      Foo.bar(1,2,3)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-12-24
        • 2011-07-14
        • 1970-01-01
        • 2017-06-05
        • 1970-01-01
        • 2019-02-04
        • 2021-10-07
        相关资源
        最近更新 更多