简体   繁体   中英

Method overloading in implicit class

I'm using a class (that I cannot modify) containing a method which receives a value of type Any as parameter, like the following example:

class Foo(value: Int) {
  def +(other: Any): Foo = ???
}

I would like to add a custom implementation for the method +() when it's used with a specific type. I would expect to be able to do something like:

implicit class RichFoo(foo: Foo) {
  def +(other: Int): Foo = ???
}

// or

implicit class RichFoo(foo: Foo) {
  def +[T <: Bar](other: T): T = ???
}

However, these approaches don't work.

Is it possible to do without extending the original class?

No.

To the compiler, implicit conversions and other rewrite rules (like those around Dynamic ) are a "last resort" of sorts. They are only applied if code does not already typecheck as-is. When you do foo + x , the compiler already knows that + takes Any , so it doesn't even try to look for implicits. If you did foo - x , and Foo had no - of the correct type, only then would the compiler search for a conversion.

Instead, you can create a method with a new name, maybe add , that is not present in Foo but is present in RichFoo . This will not, however, protect you from doing foo + 1 instead of foo add 1 , since both methods are valid.

implicit class RichFoo(foo: Foo) {
  def add(other: Int): Foo = ???
}

You can use a phantom type to track what is convertible.

scala> trait Tagged[B]
defined trait Tagged

scala> type Of[+A, B] = A with Tagged[B]
defined type alias Of

scala> class Tagger[B] { def apply[A](a: A): A Of B = a.asInstanceOf[A Of B] }
defined class Tagger

scala> object tag { def apply[B]: Tagger[B] = new Tagger[B] }
defined object tag

The given thing:

scala> case class C(i: Int) { def +(x: Any): C = C(i + x.toString.toInt) }
defined class C

and a marker trait:

scala> trait CC
defined trait CC

Normally:

scala> C(42) + "17"
res0: C = C(59)

This works:

scala> val cc = tag[CC](C(42))
cc: Of[C,CC] = C(42)

But not this:

scala> val cc = tag[CC](C(42): Any)
java.lang.ClassCastException: C cannot be cast to Tagged
  ... 29 elided

Maybe this:

scala> val cc = tag[CC](C(42): Serializable)
cc: Of[Serializable,CC] = C(42)

Then:

scala> implicit class XC(v: Serializable Of CC) {
     | def +(x: Any): C Of CC = tag[CC] {
     |   println("OK")
     |   v.asInstanceOf[C] + x
     | }}
defined class XC

Abnormally:

scala> val valueAdded = cc + "17"
OK
valueAdded: Of[C,CC] = C(59)

There's surely a better way to do this:

scala> implicit def untagit(x: Serializable Of CC): C Of CC = tag[CC](x.asInstanceOf[C]) 
untagit: (x: Of[Serializable,CC])Of[C,CC]

scala> cc.i
res9: Int = 42

because that ruins it:

scala> val res: C = cc + "17"
<console>:18: error: type mismatch;
 found   : <refinement>.type (with underlying type Of[Serializable,CC])
 required: ?{def +(x$1: ? >: String("17")): ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method XC of type (v: Of[Serializable,CC])XC
 and method untagit of type (x: Of[Serializable,CC])Of[C,CC]
 are possible conversion functions from <refinement>.type to ?{def +(x$1: ? >: String("17")): ?}
       val res: C = cc + "17"
                    ^
<console>:18: error: value + is not a member of Of[Serializable,CC]
       val res: C = cc + "17"
                       ^

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM