Reputation: 1241
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
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
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