Scala import statement incorrectly imports implicit value

I am trying to add ~= operator to Double in Scala. The below is my attempt:

package mypackage

object Implicits {

  case class MyEpsilon(value: Double) // Wrapper class for implicit argument

  implicit class MyDouble(val v: Double) extends AnyVal {
    /**
     * Return true if 2 doubles are approximately equal
     */
    def ~=(other: Double)(implicit epsilon: MyEpsilon): Boolean = {
      scala.math.abs(v - other) <= epsilon.value
    }

    /**
     * The same as above, but implicit value type is Double
     */
    def ~~=(other: Double)(implicit epsilon: Double): Boolean = {
      scala.math.abs(v - other) <= epsilon
    }
  }

  implicit val defaultDouble: Double = 1e-6
  implicit val defaultMyEpsilon: MyEpsilon = MyEpsilon(1e-6)

}

So that when we write 1.0 ~= 1.01, Scala should implicitly convert 1.0 to MyDouble class, which has ~= method.

Question: But why does the following code work for ~= without having to define an implicit value of type MyEpsilon? (On the other hand, as expected, ~~= doesn't work, because we need to define a local implicit value of type Double.)

import mypackage.Implicits.MyDouble // Try to import only "MyDouble" class

val a: Double = 1.0
val b: Double = 1.0 + 1e-3

val res0 = (a ~= b) // Why does this work without having to define implicit value of type "MyEpsilon"?

// Below doesn't work unless we define a local implicit value of type "Double"
// error: could not find implicit value for parameter epsilon: Double
val res1 = (a ~~= b)

Solution 1:

Let's make an experiment in REPL:

@ object A {
    trait B
    object B {
      trait C
      object C {
        implicit val b: B = new B {}
      }
      implicit val c: C = new C {}
    }
  } 
defined object A

@ implicitly[A.B.C] 
res3: A.B.C = ammonite.$sess.cmd2$A$B$$anon$2@13f9ad9

@ implicitly[A.B] 
cmd4.sc:1: could not find implicit value for parameter e: ammonite.$sess.cmd2.A.B
val res4 = implicitly[A.B]
                     ^
Compilation Failed

We can see that:

  • implicit of type A.B.C placed in A.B was found
  • implicit of type A.B placed in A.B.C was not found

When Scala starts looking for implicits it works more or less like this:

  • look at the current scope, are there any visible values of sought type? if so, are they annotated with implicit? (this also means that when you overshade implicit val foo: Foo with val foo: Foo implicit disappears from implicit scope)
  • if there are none look at companion objects of all the types contributing to the sought type
    • e.g. if you have Foo[(A, B[C])], compiler will search companion objects of: Foo, Tuple2, A, B and C
    • as our experiment shown nested types also count - A.B.C will trigger lookup in A, B and C

So in your case Scala would look for implicit in companions:

  • in case of MyEpsilon: in MyEpsilon and in Implicits
  • in case of Double: in Double

That's why your first example compiles and the second fails to find the implicit.