This is not the surprising part and not what’s going on here. Try this instead:
object Test extends App {
import ConvertibleOps.given
// error - "Not found: type Convertible"
type X = Convertible
}
Which is not different than how implicits work nowadays:
object external {
trait Convertible[A,B] {
def cast(a: A): B
}
object Convertible {
implicit object IntToString extends Convertible[Int, String] {
def cast(a: Int): String = s"<$a>"
}
}
}
object ConvertibleOps {
import external.Convertible
implicit class Ops[A](a: A) {
def cast[B]()(implicit c: Convertible[A,B]): B = c.cast(a)
}
}
object Test extends App {
import ConvertibleOps
// compiles
val s1 = 1.cast[String]
val s2: String = 2.cast
// doesn't
type X = Convertible
val x = IntToString
}
Which is one of the reasons why implicits are confusing. They do not only get applied in the local scope upon import
– a name resolution mechanism – but are being propagated between scopes, unlike names.