3

Problem:

Something about implicit class, confuses reduce(). When inside implicit class, compiler complains on reduce() second parameter. but when same code is inside non-implicit method it compiles and works fine.

What am I missing about implicit classes?

Code:

object ImpliCurri {
    implicit class MySeq[Int](val l: Seq[Int]) {
        //not compiling
        final def mapSum(f:Int=>Int):Int = {
            l.map(x=>f(x)).reduce(_+_)
       //compile error on reduce: Type mismatch. Expected String, fount Int
        }
    }

    // works fine
    def mySum(l:Seq[Int], f:Int=>Int):Int = {
        l.map(x=>f(x)).reduce(_+_)
        // compiles and works no issues
    }
}
Michael Zeltser
  • 399
  • 1
  • 3
  • 7

3 Answers3

5

You need to get rid of the type parameter Int. Int in the case of the implicit class is not actually the type Int, but instead it's a free type parameter that's shadowing the name of Int.

The reason for the cryptic compiler error is that the compiler is inferring the type Any from the lambda _ + _ (since the type parameter could be anything), and assuming the + will come from a toString on type Any. If you replace Int with T in the class declaration, you'll see the error is the same.

This will work:

implicit class MySeq(val l: Seq[Int]) {
    final def mapSum(f: Int => Int): Int = {
        l.map(x => f(x)).reduce(_ + _)
    }
 }
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
3

It doesn't actually have anything to do with implicits. You get the same error if it's just a regular class.

The reason is that you have declared a generic type: MySeq[Int] that just happens to be called Int. So when you say f: Int => Int you think "Oh, that's an integer" and the compiler thinks, "Oh, that means you could fill in any type there!". (Replace all your Ints with A and it would work the same.)

Now the compiler is in a bind. What + can you apply to any pair of types? Well, you can convert anything to a String, and + is defined on a String. So you get a very misleading error message when the compiler realizes that this approach won't work.

Just drop the [Int] and all your Ints will actually mean what you think they mean, and the implicit class version will work fine.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
2

Replace MySeq[Int](val l: Seq[Int]) with MySeq(val l: Seq[Int]).

Explanation of the compiler message:
The MySeq[Int] part defines an abstract type parameter for class MySeq named Int, which is (automatically) a subclass of Any and shadows the actual scala.Int. Then the compiler tries to call + method of an instance of Any. It sees a declaration of an implicit class scala.Predef.any2stringadd, which has a method with signature def +(other: String): String, so the compiler thinks the second parameter to + should be a String.

Kolmar
  • 14,086
  • 1
  • 22
  • 25