0

I have a custom type MySeq extending IndexedSeq[Int] and its implicit conversion:

package example

import scala.language.implicitConversions

class MySeq(vals: IndexedSeq[Int]) extends IndexedSeq[Int] {
  def apply(i: Int): Int = vals(i)

  def length: Int = vals.length
}

object MySeq {
  implicit def seq2MySeq(vals: Int*): MySeq = new MySeq(vals.toIndexedSeq)
}

I want to be able to call some function of type MySeq => T as follows:

package example

object Hello extends App {
  def foo(xs: MySeq): MySeq = xs

  val ret = foo(1, 2, 3)
}

But I get a Too many arguments for method foo(MySeq) error when compiling with scala-2.13.8. What am I missing/misunderstanding?

vigr
  • 3
  • 2
  • `def foo(xs: MySeq): MySeq = xs` - why would you want to do this ? There is no need for an implicit, nor it can work with variable-length argument lists. Creating an `apply(vals: Int*)` in the companion object is the idiomatic way. – Alin Gabriel Arhip Jul 09 '22 at 18:08

2 Answers2

2

You can implicitly convert only 1 value to another 1 value, you cannot use conversion to "overload" unary method into variadic method.

You could implement conversions so that

foo(Seq(1, 2, 3))

would become

foo(MySeq.seq2MySeq(Seq(1, 2, 3))

but to be able to use

foo(1, 2, 3)

you would have to overload the method e.g.

def foo(ints: Int*) = foo(MySeq(ints))
Mateusz Kubuszok
  • 24,995
  • 4
  • 42
  • 64
  • Thank you, I already tried this. I guess I just want to understand why. My reasoning is: empty sequence is a valid sequence (`Int*` included); so, were it possible to implicitly convert a call `foo(Int*)` into `foo(MySeq(Int*))`, it would imply that a call `foo()` should convert to `foo(MySeq())`. But I could just as easily have had an overload of `foo` with no arguments. That would be a runtime error at worst, and a compiler unable to decide which is it - at best. Does that make sense? – vigr Jul 09 '22 at 19:35
  • hm, given 2 methods - one with empty arguments `foo(): T` and another with sequence of ints `foo(x: Int*):T`, a call `foo()` compiles and always resolves into the latter. So there is some sort of convention? If I add a parameterless `def foo: T`, that gives an error - "method foo is defined twice". – vigr Jul 09 '22 at 19:52
  • 1
    It doesn't work that way: compiler first checks functions possible arities (here: `foo(MySeq)`. Than it sees _3_ arguments, while there is no function with 3 arguments, therefore it fails. Sequence of several values is treated as variadic only if there is vararg definition in the arity. Conversions are considered only after the number of argument checks and just some of them have wrong types. – Mateusz Kubuszok Jul 10 '22 at 09:14
0

implicit conversions can be useful, but they have always been easy to misuse.

From the Baeldung Blog:

The major drawbacks with implicit in Scala 2 are:

  • Usage of the keyword implicit for unrelated functionalities
  • Unintended conversions due to implicit conversions
  • Unclear compiler error messages

You said you are still using 2.13.8, and this post talks mostly about what has changed in Scala 3, but the caveats of implicit can be difficult to debug and understand, and they are highlighted at the beginning of this post.

In Scala 3, for these reasons (and others), they have reworked implicits (and introduced a new system that is far more explicit and powerful given/using [but that's a whole 'nother can of worms; very powerful and useful, but only in Scala 3+])

In your case, I would recommend a simple overload to your apply method for MySeq, no implicit conversion necessary:

class MySeq( vals: IndexedSeq[Int] ) extends IndexedSeq[Int] {

    // Unchanged
}
    
object MySeq {

    def apply( vals: Int* ): MySeq = new MySeq( vals.toIndexedSeq )
}
    
@main
def runner(): Unit = {

    // use overloaded apply, convenient but explicit conversion
    val mySeqFromVarArg: MySeq = MySeq( 1, 2, 3 )
}