Search code examples
scalaimplicit-conversionimplicitcompanion-object

When is an implicit imported from a companion object?


I know this is the umpteenth implicits / companion object question. However, I did not find this case anywhere yet.

In "Tryout", why does A need no import for the implicit class, while B does need it? Thankx.

class LongConsumer {
  def consume(l: Long) = Unit
}

case class X(x: Long)

object X extends (Long => X) {
  implicit class ConsumeX(val c: LongConsumer) extends AnyVal {
    def consume(x: X) = c consume x.x
  }
  implicit class EatX(val c: LongConsumer) extends AnyVal {
    def eat(x: X) = c consume x.x
  }
}

object Tryout {
  // A: does not need import to compile - why?
  new LongConsumer().consume(X(10L))

  // B: needs import to compile - why?
  import X.EatX
  new LongConsumer().eat(X(10L))
}

Solution

  • There are only three cases for views.

    http://www.scala-lang.org/files/archive/spec/2.11/07-implicit-parameters-and-views.html#views

    Your case A is #1, where X is not a Long, as expected by consume. (Edit: Sorry, I'm watching Hulu. It's #3, where consume doesn't apply. The conversion is from LongConsumer to ConsumeX. For that case, I wouldn't have expected the implicit scope of X to be in play.)

    But notice that the implicit scope is the scope for X => Long.

    The scope of Function[X, Long] includes the scope of both type params, and the scope of X includes the companion X. (The previous section, 7.2, lists what's in the implicit scope.) The intuition is that you have one type in hand but need the other; either type can provide the conversion.

    In your case B, it's case #2, and the scope of LongConsumer doesn't supply a conversion.

    I asked on the ML for clarification. -Ytyper-debug says it's the second try.

    |    |    second try: (l: Long)Unit.type and eatery.X.type
    |    |    |-- eatery.X.type EXPRmode (silent: value <local Tryout> in Tryout)
    |    |    |    |-- X.apply BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Tryout> in Tryout)
    |    |    |    |    \-> (x: Long)eatery.X
    |    |    |    \-> eatery.X
    |    |    |-- eatery.this.X.ConsumeX BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Tryout> in Tryout) implicits disabled
    |    |    |    \-> eatery.X.ConsumeX.type <and> (c: eatery.LongConsumer)eatery.X.ConsumeX
    |    |    |-- (c: eatery.LongConsumer)eatery.X.ConsumeX EXPRmode-POLYmode-QUALmode (site: value <local Tryout> in Tryout)
    |    |    |    \-> eatery.X.ConsumeX
    |    |    |-- eatery.this.X.ConsumeX(new LongConsumer()).consume BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Tryout> in Tryout)
    |    |    |    \-> (x: eatery.X)Unit.type (with underlying type (x: eatery.X)Unit.type)
    |    |    \-> Unit.type
    |    \-> [object Tryout] eatery.Tryout.type
    

    Results are in:

    https://issues.scala-lang.org/browse/SI-5089

    @retronym says: The broader question of why the implicit scope includes companions of the argument types of the applications is known as the "1 + BigInteger(1) problem"

    Kind of obvious when you put it that way.

    Also see enter link description here under "Implicit scope of an argument's type."