ambiguous implicits resolution fails silently when they are named the same

221 views
Skip to first unread message

Eugene Yokota

unread,
Mar 25, 2013, 12:25:51 AM3/25/13
to scala-l...@googlegroups.com
Hi,

There have been a few 2.10 [implicits][2] [discussions][2] already,
but I'm seeing them popping up around me,
so let me bring it up here. Sorry if it's been reported already.

### ambiguous reference in Scala 2.9.2

Let's first see how ambiguous reference is reported in 2.9.2.

    scala> :paste
    // Entering paste mode (ctrl-D to finish)

    object X {  
      implicit def foo = 1
    }

    object Y {
      implicit def foo = 2
    }

    object Z {
      import X._
      import Y._
      println(foo)
    }

    // Exiting paste mode, now interpreting.

    <console>:18: error: reference to foo is ambiguous;
    it is imported twice in the same scope by
    import Y._
    and import X._
             println(foo)
                     ^

### ambiguous implicit reference in Scala 2.9.2

Next we implicitly use foo.

    scala> :paste
    // Entering paste mode (ctrl-D to finish)

    object X {  
      implicit def foo = 1
    }

    object Y {
      implicit def foo = 2
    }

    object Z {
      import X._
      import Y._
      println(implicitly[Int])
    }

    // Exiting paste mode, now interpreting.

    defined module X
    defined module Y
    defined module Z

    scala> Z
    2
    res0: Z.type = Z$@8c96c01

Magically it works.

### ambiguous implicit reference in Scala 2.10.1

Now let's run this on 2.10.1.

    scala> :paste
    // Entering paste mode (ctrl-D to finish)

    object X {  
      implicit def foo = 1
    }

    object Y {
      implicit def foo = 2
    }

    object Z {
      import X._
      import Y._
      println(implicitly[Int])
    }

    // Exiting paste mode, now interpreting.

    <console>:19: error: could not find implicit value for parameter e: Int
             println(implicitly[Int])
                               ^

When the implicit defs are named the same, the implicit resolution fails siliently.

### ambiguous implicit reference in Scala 2.10.1 with different name

To prove this point, let me rename one of the foos to bar.

    scala> :paste
    // Entering paste mode (ctrl-D to finish)

    object X {  
      implicit def foo = 1
    }

    object Y {
      implicit def bar = 2
    }

    object Z {
      import X._
      import Y._
      // println(foo)
      println(implicitly[Int])
    }

    // Exiting paste mode, now interpreting.

    <console>:19: error: ambiguous implicit values:
     both method foo in object X of type => Int
     and method bar in object Y of type => Int
     match expected type Int
             println(implicitly[Int])
                               ^

### what I would expect for foo and foo situation

I think it's a bug that the compiler doesn't make it clear
that X.foo and Y.foo are ambiguous, and I'd expect something like:

    <console>:19: error: ambiguous implicit values:
     both method foo in object X of type => Int
     and method foo in object Y of type => Int
     match expected type Int
             println(implicitly[Int])
                               ^

Thanks to Jason, I am aware of -Xlog-implicits, but I shouldn't have to
use it to find out ambiguous imports.

The problem is that it's not clear where the conflicting implicits
are coming from especially from the library users point of view.
It's sometimes not even clear that the compile error is due to implicit
conversion. It'd say something like "required: treehugger.forest.TermName."

Thanks,
-eugene

### notes

- [xuwei_k blogged about this topic in Japanese][3]
- [treehugger.scala stopped working for 2.10][4]
- [Scala compiler error when using Scalaz7 and Json4s][5] (possibly related?)


Paul Phillips

unread,
Mar 25, 2013, 1:35:41 PM3/25/13
to scala-l...@googlegroups.com
On Sun, Mar 24, 2013 at 9:25 PM, Eugene Yokota <eed3...@gmail.com> wrote:
I think it's a bug that the compiler doesn't make it clear
that X.foo and Y.foo are ambiguous, and I'd expect something like:

    <console>:19: error: ambiguous implicit values:
     both method foo in object X of type => Int
     and method foo in object Y of type => Int
     match expected type Int
             println(implicitly[Int])
                               ^

Another way to clarify the matter is to make them non-implicit and try to invoke it directly. Again we get a useful error message:

object X { def foo = 1 }
object Y { def foo = 2 }
object Z {
  import X._
  import Y._
  println(foo)
}
// a.scala:6: error: reference to foo is ambiguous;
// it is imported twice in the same scope by
// import Y._
// and import X._
//   println(foo)
//           ^
// one error found

One could issue this ambiguity error with one additional word ("implicit reference to foo is ambiguous") and it would perhaps be better, because the error message you suggest implies that you could have both those same-named implicits operational if only their types were distinct - but you can't, there can only be one member with the simple name "foo" at any given point.

The main obstacle to fixing this is that the error reporting code is too hard to understand. My guess is that implicit search runs into this error and suppresses it, then forgets what happened and reports a generic error.

Jason Zaugg

unread,
Mar 25, 2013, 2:04:10 PM3/25/13
to scala-l...@googlegroups.com
On Mon, Mar 25, 2013 at 6:35 PM, Paul Phillips <pa...@improving.org> wrote:

The main obstacle to fixing this is that the error reporting code is too hard to understand. My guess is that implicit search runs into this error and suppresses it, then forgets what happened and reports a generic error.

That particular elaboration is correctly reported under -Xlog-implicits.

qbin/scala -Xlog-implicits
Welcome to Scala version 2.11.0-20130325-104203-0e1b8be3e5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_37).
Type in expressions to have them evaluated.
Type :help for more information.

scala> :paste
// Entering paste mode (ctrl-D to finish)

object X { def foo = 1 }
object Y { def foo = 2 }
object Z {
  import X._
  import Y._
  println(foo)
}

// Exiting paste mode, now interpreting.

<console>:12: error: reference to foo is ambiguous;
it is imported twice in the same scope by
import Y._
and import X._
         println(foo)
                 ^ 

But I'll take a closer look at Eugene's examples and see what we can learn.

-jason

Paul Phillips

unread,
Mar 25, 2013, 2:14:21 PM3/25/13
to scala-l...@googlegroups.com
As a slight tangent, it's interesting to consider that this compiles:

object X { implicit def foo = 1 }
object Y { implicit def foo = 2 }
object Z {
  import X._
  {
    import Y._
    println(implicitly[Int])
  }
}

...which is expected because the import in a nested scope has sufficient precedence to shadow the earlier foo. But this does not compile:

object X { implicit def foo1 = 1 }
object Y { implicit def foo2 = 2 }
object Z {
  import X._
  {
    import Y._
    println(implicitly[Int])
  }
}

...which is also expected because implicit resolution does not offer the same precedence mechanisms as does name resolution, BUT if I were new here it would be my intuition that this compiles. That is, I'd fully expect that importing an implicit of type T in an inner scope would trump the implicit T from some outer scope - for *exactly* the same reason as it does by name.

Someone remind me why it doesn't work that way?

eugene yokota

unread,
Mar 25, 2013, 2:23:12 PM3/25/13
to scala-l...@googlegroups.com

On Mon, Mar 25, 2013 at 1:35 PM, Paul Phillips <pa...@improving.org> wrote:

One could issue this ambiguity error with one additional word ("implicit reference to foo is ambiguous") and it would perhaps be better, because the error message you suggest implies that you could have both those same-named implicits operational if only their types were distinct - but you can't, there can only be one member with the simple name "foo" at any given point.

+1. Basing it off of "reference to foo is ambiguous" is better.

    <console>:18: error: reference to foo is ambiguous;
    it is imported twice in the same scope by
    import Y._
    and import X._
             println(foo)
                     ^

-eugene

eugene yokota

unread,
Mar 25, 2013, 2:52:08 PM3/25/13
to scala-l...@googlegroups.com

On Mon, Mar 25, 2013 at 2:14 PM, Paul Phillips <pa...@improving.org> wrote:
But this does not compile:

object X { implicit def foo1 = 1 }
object Y { implicit def foo2 = 2 }
object Z {
  import X._
  {
    import Y._
    println(implicitly[Int])
  }
}

...which is also expected because implicit resolution does not offer the same precedence mechanisms as does name resolution, BUT if I were new here it would be my intuition that this compiles. That is, I'd fully expect that importing an implicit of type T in an inner scope would trump the implicit T from some outer scope - for *exactly* the same reason as it does by name.

Someone remind me why it doesn't work that way?


There are both foo1 and foo2 available for the second example.
SLS says:

> If there are several eligible arguments which match the implicit parameter’s type, a most specific one will be chosen using the rules of static overloading resolution (§6.26.3).

6.26.3 starts out as:

> If an identifier or selection e references several members of a class,

so the entire clause is ignored, and we still have foo1 and foo2 to pick from.

When I thought about the clumsiness of the name shadowing of implicits before,
I imagined `import _ => _` could reset all implicits from the local scope so the lib users wouldn't have to dig around source code to unimport implicits.

-eugene

Paul Phillips

unread,
Mar 25, 2013, 3:19:37 PM3/25/13
to scala-l...@googlegroups.com
On Mon, Mar 25, 2013 at 11:52 AM, eugene yokota <eed3...@gmail.com> wrote:
There are both foo1 and foo2 available for the second example.

Right, when I say "remind me why it doesn't work that way" I mean "remind me why it is not specified to work that way, because that way seems better", not "what does the specification say."
 
When I thought about the clumsiness of the name shadowing of implicits before,
I imagined `import _ => _` could reset all implicits from the local scope so the lib users wouldn't have to dig around source code to unimport implicits.

If that did anything, it would have to remove every identifier from scope.

But I'll reiterate the question: why wouldn't we want the more deeply nested implicit import to have higher precedence than the less deeply nested? What arguments are there in favor of resolving names this way which do not apply equally well to implicit values?

eugene yokota

unread,
Mar 25, 2013, 7:47:19 PM3/25/13
to scala-l...@googlegroups.com
The 2.10 behavior is consistent in the semantics of the import statement,
which loads some symbols into the current scope, and no magic.
Scoping of implicits due to curly brace and name shadowing are almost happy side-effect of
piggybacking on the symbol table like normal defs/vals/classes.

Your proposal on shadowing based on curly brace and type (I assume) feels ad-hoc
because it's hard to explain the behavior based on current naming binding rules.

What if we introduce yet another meaning to "implicit", and implement a sugar like

object X { implicit def foo1 = 1 }
object Y { implicit def foo2 = 2 }
object Z {
  implicit import X._
  {
    implicit import Y._
    println(implicitly[Int])
  }
}

where the implicit import would only import implicit val/def/class,
and rename them to a canonical name based on the resulting type,
e.g. it expands to `import X.{ foo1 => $implicitInt}`?

That way you can still explain away the shadowing behavior based on the current rules.

-eugene

Paul Phillips

unread,
Mar 25, 2013, 7:51:23 PM3/25/13
to scala-l...@googlegroups.com

On Mon, Mar 25, 2013 at 4:47 PM, eugene yokota <eed3...@gmail.com> wrote:
Your proposal on shadowing based on curly brace and type (I assume) feels ad-hoc
because it's hard to explain the behavior based on current naming binding rules.

I wouldn't call it "shadowing", I'd call it "higher precedence". When I call an overloaded method, I don't consider the specific method chosen for dispatch to "shadow" the others.

Ruslan Shevchenko

unread,
Mar 26, 2013, 11:42:35 AM3/26/13
to scala-l...@googlegroups.com
just note about simular thread in scala-sip some time ago:   https://groups.google.com/forum/?fromgroups=#!topic/scala-sips/m1ob6s_-puU



--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Reply all
Reply to author
Forward
0 new messages