具有重复参数的磁铁图案(可变参数) [英] Magnet pattern with repeated parameters (varargs)

查看:89
本文介绍了具有重复参数的磁铁图案(可变参数)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否可以将磁体模式与varargs:

Is it possible to use the magnet pattern with 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"

?

推荐答案

正如gourlaysama所提到的,将可变参数转换为单个Product可以解决问题,从语法上来说:

As already mentioned by gourlaysama, turning the varargs into a single Product will do the trick, syntactically speaking:

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

这允许以下调用进行编译:

This allows the following call to compile fine:

Foo.bar(1,2,3)

这是因为编译器自动将3个参数提升为Tuple3[Int, Int, Int].它将与任意数量的参数一起使用,直到arity为22.现在的问题是如何使它的类型安全.因为它是Product.productIterator是返回方法主体内部的参数列表的唯一方法,但它返回Iterator[Any].我们不能保证仅使用Int来调用该方法.这一点不足为奇,因为我们实际上甚至从未在我们只想要Int的签名中提到.

This is because the compiler autmatically lifts the 3 arguments into a Tuple3[Int, Int, Int]. This will work with any number of arguments up to an arity of 22. Now the problem is how to make it type safe. As it is Product.productIterator is the only way to get back our argument list inside the method body, but it returns an Iterator[Any]. We don't have any guarantee that the method will be called only with Ints. This should come as no surprise as we actually never even mentioned in the signature that we wanted only Ints.

好,因此不受约束的Product和变量列表之间的主要区别在于,在后一种情况下,每个元素都属于同一类型.我们可以使用类型类对此进行编码:

OK, so the key difference between an unconstrained Product and a vararg list is that in the latter case each element is of the same type. We can encode this using a type class:

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)

我们更改了方法签名形式

We have changed the method signature form

implicit def fromInts(t: Product)

收件人:

implicit def fromInts[P]( xs: P )( implicit evidence: P IsVarArgsOf Int )

在方法主体内部,我们使用新的方法args取回我们的arg列表.

Inside the method body, we use the new methodd args to get our arg list back.

请注意,如果我们尝试使用不是Int s元组的元组来调用bar,则会出现编译错误,这使我们的类型安全性得到恢复.

Note that if we attempt to call bar with a a tuple that is not a tuple of Ints, we will get a compile error, which gets us our type safety back.

更新:如0__所示,我的上述解决方案不适用于数字加宽.换句话说,以下内容不会编译,尽管如果bar仅使用3个Int参数会起作用:

UPDATE: As pointed by 0__, my above solution does not play well with numeric widening. In other words, the following does not compile, although it would work if bar was simply taking 3 Int parameters:

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

要解决此问题,我们所需要做的就是修改IsVarArgsOf,以便所有隐式允许 元组elemts可以转换为通用类型,而不是所有类型都相同:

To fix this, all we need to do is to modify IsVarArgsOf so that all the implicits allow the tuple elemts to be convertible to a common type, rather than all be of the same type:

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 ...
}

好的,实际上我撒谎了,我们还没有完成.因为我们现在接受不同类型的元素(只要它们可以转换为通用类型,就不能将它们强制转换为期望的类型(这将导致运行时强制转换错误),而必须应用隐式转换.我们可以像这样重做它:

OK, actually I lied, we're not done yet. Because we are now accepting different types of elements (so long as they are convertible to a common type, we cannot just cast them to the expected type (this would lead to a runtime cast error) but instead we have to apply the implicit conversions. We can rework it like this:

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

这解决了数字扩展的问题,当混合不相关的类型时,我们仍然可以编译:

This fixes the problem with numeric widening, and we still get a compile when mixing unrelated types:

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

这篇关于具有重复参数的磁铁图案(可变参数)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆