shayan
shayan

Reputation: 1241

Implicit argument in constructor vs method signature

What difference does the compiler see in BroFinder1 and BroFinder2 that causes the first one to fail? I really need BroFinder1 to work as it is without utilizing patterns such as Aux Pattern.

trait Brother[I] {
  type Bro
  def get: Bro
}

class Foo
object Foo {
  implicit object bro extends Brother[Foo] {
    override type Bro = Bar

    override def get = new Bar
  }
}

class Bar {
  def barSpecificAction(): Unit = ()
}

class BroFinder1[I](implicit val b: Brother[I]) {
  def brotherOf: b.Bro = b.get
}

class BroFinder2[I] {
  def brotherOf(implicit b: Brother[I]): b.Bro = b.get
}


new BroFinder1[Foo].brotherOf.barSpecificAction() // Doesn't compile
//error: Error:(73, 32) value barSpecificAction is not a member of _1.b.Bro
//new BroFinder1[Foo].brotherOf.barSpecificAction();
                          ^
new BroFinder2[Foo].brotherOf.barSpecificAction() // Ok

//scala version: 2.12.4

Upvotes: 1

Views: 112

Answers (2)

Dmytro Mitin
Dmytro Mitin

Reputation: 51658

Because new BroFinder2[Foo].brotherOf is of type Bar and Bar has method barSpecificAction but new BroFinder1[Foo].brotherOf is of existential type

x.Bro forSome { val x: Brother[Foo] }

and it doesn't have method barSpecificAction.

Upvotes: 3

SergGr
SergGr

Reputation: 23788

This is not a perfect answer but probably will provide some insights. The issue seems to be not related to implicit at all. It seems to be related to another fairly advance Scala feature: path-dependent types. Particularly the trouble seems to come from the fact that Scala type system is not powerful enough to express type difference between finder1 and finder2 precisely in following code:

object Foo {

  implicit object bro extends Brother[Foo] {
    override type Bro = Bar

    override def get = new Bar
  }

  object bro2 extends Brother[Foo] {
    override type Bro = Foo

    override def get = new Foo
  }

}


val finder1 = new BroFinder1[Foo]
val finder2 = new BroFinder1[Foo]()(Foo.bro2)
val bof1 = finder1.brotherOf
val bof2 = finder2.brotherOf

Sidenote: the fact that some parameter is implicit doesn't make it a "constant" because you can always pass the parameter explicitly anyway or you can have different visible implicit values in different contexts.

AFAIU the most precis type the finder1 or finder2 might be assigned in this example is just BroFinder1[Foo]. Particularly there is no way to catch different values of the b implicit variable and thus there is no way to pass further exactly the value of the path-dependent type that is encoded inside that value. Thus the best compiler knows about the types of bof1 and bof2 is that they both have type in form of _1.b.Bro where _1 means some particular instance of BroFinder1[Foo]. And so the compiler can't be sure that bof1 is actually of type Bar while bof2 is of type Foo.

The second example works because implicit parameter is captured in the same context so compiler knows exactly what the result type of brotherOf is.

The only workaround I know is not exactly suitable in your case: use "Aux" type. If your BroFinder took also an explicit parameter of type Foo, the solution might have looked like this:

type BrotherAux[I, B] = Brother[I] {type Bro = B}

class BroFinder3[I, B](val v: I)(implicit val b: BrotherAux[I, B]) {
  def brotherOf: B = b.get
}

new BroFinder3(new Foo).brotherOf.barSpecificAction()

But in your case it has to be much less helpful

class BroFinder1[I, B](implicit val b: BrotherAux[I, B]) {
  def brotherOf: B = b.get
}

new BroFinder1[Foo, Bar].brotherOf.barSpecificAction()

Upvotes: 3

Related Questions