6
votes

Using scala 2.10.3, my goal is to make the following work:

object A {
  implicit class Imp(i: Int) {
    def myPrint() {
      println(i)
    }
  }
}

object B {
  implicit class Imp(i: String) {
    def myPrint() {
      println(i)
    }
  }
}

import A._
import B._

object MyApp extends App {
  3.myPrint()
}

This fails with

value myPrint is not a member of Int

If I give A.Imp and B.Imp different names (for example A.Imp1 and B.Imp2), it works.

Diving a bit deeper into it, there seems to be the same problem with implicit conversions.

This works:

object A {
  implicit def Imp(i: Int) = new {
    def myPrint() {
      println(i)
    }
  }

  implicit def Imp(i: String) = new {
    def myPrint() {
      println(i)
    }
  }
}

import A._

object MyApp extends App {
  3.myPrint()
}

Whereas this doesn't:

object A {
  implicit def Imp(i: Int) = new {
    def myPrint() {
      println(i)
    }
  }
}

object B {
  implicit def Imp(i: String) = new {
    def myPrint() {
      println(i)
    }
  }
}

import A._
import B._

object MyApp extends App {
  3.myPrint()
}

Why? Is this a bug in the scala compiler? I need this scenario, since my objects A and B derive from the same trait (with a type parameter) which then defines the implicit conversion for its type parameter. In this trait, I can only give one name for the implicit conversion. I want to be able to import more of these objects into my scope. Is there a way to do that?

edit: I can't give the implicit classes different names, since the examples above are only breaking down the problem. My actual code looks more like

trait P[T] {
  implicit class Imp(i: T) {
    def myPrint() {
      ...
    }
  }
}

object A extends P[Int]
object B extends P[String]

import A._
import B._
3
It sounds like all you need is the member function to be the same name, not the implicit name itself. Why do you need to call both "Imp?"wheaties
I have only one place where I define the implicit. This is in the generic parent trait. Therefore I can only give one name to it. All objects then inherit from that trait and allow using it for different types.Heinzi

3 Answers

8
votes

The implicits just have to be available as a simple name, so you can rename on import.

Just to verify:

scala> import A._ ; import B.{ Imp => BImp, _ }
import A._
import B.{Imp=>BImp, _}

scala> 3.myPrint
3
0
votes

Actually, it works if you replace

import A._
import B._

with

import B._
import A._

What happens, I think, is that A.Imp is shadowed by B.Imp because it has the same name. Apparently shadowing applies on the function's name and do not take the signature into account. So if you import A then B, then only B.Imp(i: String) will be available, and if you import B then A, then only A.Imp(i: Int) will be available.

If you need to use both A.Imp and B.Imp, you must rename one of them.

0
votes

In case anyone else runs into this issue, there is a partial workaround, which I found here:
https://github.com/lihaoyi/scalatags/blob/3dea48c42c5581329e363d8c3f587c2c50d92f85/scalatags/shared/src/main/scala/scalatags/generic/Bundle.scala#L120

That code was written by Li Haoyi, so you can be pretty confident that no better solution exists...

Essentially, one can still use traits to define methods in terms of each other, but it will require boilerplate to copy those implicits into unique names. This example might be easier to follow:

trait Chainable
object Chainable {
  implicit val _chainableFromInt     = IntChainable.chainable _
  implicit val _chainableFromIntTrav = IntChainable.traversable _
  implicit val _chainableFromIntOpt  = IntChainable.optional _
  implicit val _chainableFromIntTry  = IntChainable.tried _
  implicit val _chainableFromDom     = DomChainable.chainable _
  implicit val _chainableFromDomTrav = DomChainable.traversable _
  implicit val _chainableFromDomOpt  = DomChainable.optional _
  implicit val _chainableFromDomTry  = DomChainable.tried _
  private object IntChainable extends ImplChainables[Int] {
    def chainable(n:Int) = Constant(n)
  }
  private object DomChainable extends ImplChainables[dom.Element]{
    def chainable(e:Element) = Insertion(e)
  }
  private trait ImplChainables[T] {
    def chainable(t:T):Chainable
    def traversable(trav:TraversableOnce[T]):Chainable =
      SeqChainable(trav.map(chainable).toList)
    def optional(opt:Option[T]):Chainable =
      opt match {
        case Some(t) => chainable(t)
        case None => NoneChainable
      }
    def tried(tried:Try[T]):Chainable =
      optional(tried.toOption)
  }
}

In other words, never write:

trait P[T] {
  implicit def foo(i: T) = ...
}
object A extends P[X]

Because defining implicits in type parameterized traits will lead to these naming conflicts. Although, incidentally, the trait in mentioned in the link above is parameterized over many types, but idea there is that none of the implementations of that trait are ever needed in the same scope. (JSDom vs Text, and all._ vs short._ for those familiar with Scalatags)

I also recommend reading: http://www.lihaoyi.com/post/ImplicitDesignPatternsinScala.html
It does not address this issue specifically but is an excellent summary of how to use implicits.

However, putting all these pieces together, this still seems to be an issue:

trait ImplChainables[AnotherTypeClass]{
  type F[A] = A=>AnotherTypeClass
  implicit def transitiveImplicit[A:F](t: A):Chainable
  implicit def traversable[A:F](trav: TraversableOnce[A]):Chainable = ...
}

What this trait would allow is:

object anotherImpl extends ImplChainables[AnotherTypeClass] {...}
import anotherImpl._
implicit val string2another: String=>AnotherTypeClass = ...
Seq("x"):Chainable

Because of the type parameter and the context binding (implicit parameter) those cannot be eta-expanded (i.e: Foo.bar _ ) into function values. The implicit parameter part has been fixed in Dotty: http://dotty.epfl.ch/blog/2016/12/05/implicit-function-types.html

I do not know if a complete solution would be possible, needless to say this is a complex problem. So it would be nice if same name implicits just worked and the whole issue could be avoided. And in any case, adding an unimport keyword would make so much more sense than turning off implicits by shadowing them.