Implicit conversion vs operators

#1

version: 2.12.7

Am I doing something wrong or this is a bug, or how can I make the + operator work?


trait Adder[A] {
  def add(s: A, i: Int): A
}

case class MyTest(i: Int)

implicit val myAddable: Adder[MyTest] = new Adder[MyTest] {
  def add(s: MyTest, i: Int) = MyTest(s.i + i)
}

implicit final class Ops[A](val lhs: A)(implicit adder: Adder[A]) {
  def +[B](i: B)(implicit n: Numeric[B]) = {
    adder.add(lhs, n.toInt(i))
  }
  def add[B](i: B)(implicit n: Numeric[B]) = {
    adder.add(lhs, n.toInt(i))
  }
}

val a = MyTest(2)

a.add(5)
a + 5

Here the a.add(5) works as expected, and the a + 5 is not working, I think bcs of the needed implicit parameter of the (5)(numeric[Int]).

So I went a bit further and refactored it a bit:

trait Adder[A] {
  def add(s: A, i: Int): A
}

trait NumericForOps[A] {
  val a: A
  val fa: Numeric[A]
}

object NumericForOps {
  def apply[A](s: A)(implicit ev: Numeric[A]): NumericForOps[A] =
    new NumericForOps[A] {
      val a = s
      val fa = ev
    }
}

implicit def autoNumericForOps[A](s: A)(implicit ev: Numeric[A]): NumericForOps[A] = NumericForOps(s)

case class MyTest(i: Int)

implicit val myAddable: Adder[MyTest] = new Adder[MyTest] {
  def add(s: MyTest, i: Int) = MyTest(s.i + i)
}

implicit final class Ops[A](val lhs: A)(implicit adder: Adder[A]) {
  def +[B](i: NumericForOps[B]) = {
    adder.add(lhs, i.fa.toInt(i.a))
  }
  def add[B](i: NumericForOps[B]) = {
    adder.add(lhs, i.fa.toInt(i.a))
  }
}

val a = MyTest(2)

a.add(5)
a + 5

If I desugar with IntelliJ they both desugared to the “same” code. So why the a+5 is not working? And how can I make it work?

#2

This works:

object any2stringadd

trait Adder[A] {
  def add(s: A, i: Int): A
}

case class MyTest(i: Int)

implicit val myAddable: Adder[MyTest] = new Adder[MyTest] {
  def add(s: MyTest, i: Int) = MyTest(s.i + i)
}

implicit final class Ops[A](val lhs: A)(implicit adder: Adder[A]) {
  def +[B](i: B)(implicit n: Numeric[B]) = {
    adder.add(lhs, n.toInt(i))
  }
  def add[B](i: B)(implicit n: Numeric[B]) = {
    adder.add(lhs, n.toInt(i))
  }
}

val a = MyTest(2)

a.add(5)
a + 5

I only added the first line.

So the reason it didn’t work is that there is an implicit conversion any2stringadd which adds a + method to Any. When the compiler now looks for a + method it finds your implicit conversion and any2stringadd, doesn’t know which to choose and compilation fails.

The reason my code worked is that my any2stringadd shadows Predef.any2stringadd so it is no longer eligible as an implicit conversion.

#3

Hmm. In one way: “Clever” In other ways: “Why???” Btw IntelliJ has 0 idea what happens, but the code compiles as expected :smiley:
I started to refactor a codebase which transforms an inner ADT structure to languages, and I didn’t want to break hardly the already existing code, I think this will work as a minimal effort refactor, and I hope nobody uses “+” and “-” between those things.

Thanks the help and the explanation!

#4

Basically, any2stringadd seemed like a good idea at the time, back in the early days of the language, since being able to concatenate anything to a String was convenient for building complex Strings. But in the years since, we’ve gained string interpolation (generally a better way to do it), and this has proven to be a frequently inconvenient misfeature.

I think there’s a fair consensus that it’s more trouble than it’s worth. Personally, I’m hoping it goes away in Scala 3.

#5

Note that any2stringadd is formally deprecated in Scala 2.13:

scala 2.13.0> (new AnyRef) + "foo"
               ^
              warning: method any2stringadd in object Predef is deprecated (since 2.13.0): Implicit injection of + is deprecated. Convert to String to call +
res1: String = java.lang.Object@24e83d19foo
scala 2.13.0> 3 + "foo"
                ^
              warning: method + in class Int is deprecated (since 2.13.0): Adding a number and a String is deprecated. Use the string interpolation `s"$num$str"`
res0: String = 3foo
2 Likes