Repeating Parameter Magnet Model (varargs)

Is it possible to use a chart 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" 

?

+7
source share
4 answers

As gourlaysama already mentioned, turning varargs into one Product will do the trick, syntactically saying:

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

This allows the following compilation:

 Foo.bar(1,2,3) 

This is because the compiler automatically enters 3 arguments into Tuple3[Int, Int, Int] . This will work with any number of arguments up to level 22. Now the problem is how to make it safe. Since Product.productIterator is the only way to return our list of arguments inside the method body, it returns Iterator[Any] . We have no guarantee that the method will only be called with Int s. This should not be surprising, since we never even mentioned in the signature that we only wanted Int s.

OK, so the key difference between an unlimited Product and a vararg list is that in the latter case, each item has the same type. We can code this using a class like:

 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 changed the method signature form

 implicit def fromInts(t: Product) 

in

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

Inside the body of the method, we use the new d args method to return our list of arguments.

Note that if we try to call bar with a tuple that is not an Int s tuple, we get a compilation error that returns us our type protection.


UPDATE . As pointed out by 0__, my solution above does not work well with numeric extension. In other words, the following does not compile, although it will work if bar just accepts 3 Int parameters:

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

To fix this, we only need to change IsVarArgsOf so that all implications allowed tuple elements must be converted to a common type, and not all of one 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 ... } 

Well, actually I lied, we are not done yet. Since we now accept different types of elements (while they are being converted to a common type, we cannot just attribute them to the expected type (this will lead to a runtime error at runtime), but instead we must use implicit conversions. We can redo 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 numerical extension issue, and we still get compilation 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") ^ 
+2
source

Edit:

Implicit var-args will never be selected because duplicate parameters are not really first-class citizens when it comes to types ... they are present only when checking the applicability of the method to the arguments.

Basically, when you call Foo.bar(1,2,3) , it checks to see if bar exists with variable arguments, and since it is not, it does not apply to arguments. And he cannot go further:

If you called it with a single argument, it would look for an implicit conversion from the type of the argument to the expected type, but since you called several arguments, there is a problem with arity, converting multiple arguments to one, with an implicit type conversion.


But: there is a solution using auto-tupling.

 Foo.bar(1,2,3) 

can be understood by the compiler as

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

which means that an implicit one like this will work:

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

The problem is that the only way to get the arguments is through t.productIterator , which returns Iterator[Any] and should be discarded.

Thus, you will lose type safety; this will be compiled (and not executed at runtime when using it):

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

We can make this completely safe with Scala 2.10 macros. The macro simply verifies that the argument is indeed TupleX[Int, Int, ...] and only makes it available as an implicit conversion if it passes this validation.

To make this example more useful, I modified Values to support Int arguments:

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

With macro implementation:

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

And finally, the definition of Foo as follows:

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

You get a type-safe call with the syntax just like duplicate parameters:

 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)) 
+1
source

The specification only specifies the type of repeated parameters (varargs) inside the function:

The type of such a repeating parameter inside the method is the sequence type scala.Seq [T].

It does not cover the type elsewhere.

Therefore, I assume that the compiler inside - in a certain phase - cannot match types.

From this observation (this does not compile => "double definition"):

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

Seq seems []. So the next attempt is to do it differently:

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

it compiles, but it does not solve the real problem.

The only workaround I found was to convert varargs to a sequence explicitly:

 def varargs(xs: Int*) = xs // return type is Seq[Int] Foo.bar(varargs(1, 2, 3)) 

but that, of course, is not what we want.

Perhaps related: the implicit conversion function has only one parameter. But from a logical (or temporary) compiler in the case of varargs, it can also be multiple.

Regarding types, this may be of interest

0
source

Here is a solution that uses overload (which I would rather not do)

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

All Articles