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