1

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
shayan
  • 1,211
  • 9
  • 12

2 Answers2

3

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()
SergGr
  • 23,570
  • 2
  • 30
  • 51
  • gosh I was really hoping Aux wasn't the solution. too many type parameters aside from making code ugly, also in my case caused some implicit resolution issues that only after introducing type members in code were solved. maybe I should have asked that question – shayan Dec 08 '17 at 00:01
  • @shayan, usually making the question as close to the real thing as possible is a good idea. You can wait a while for other answers and then if there is no better ones, ask a new question with a closer example (and probably reference this one to get more meaningful answers). – SergGr Dec 08 '17 at 00:04
  • @shayan, still I wonder why you really want a fully-independent "brother" type instead of just implicit wrapper class such as `RichInt` or `StringOps` – SergGr Dec 08 '17 at 00:11
  • do you mean ```implicit class ExtensionMethods[T](t: T) {def brotherOf[B](implicit b: Brother[T]): b.Bro = b.get}``` ? sry I wasn't sure what u meant. – shayan Dec 08 '17 at 00:36
  • @shayan, no I meant getting rid of `brotherOf` altogether and moving all `barSpecificAction` methods to something like `implicit class FooBrotherMethods(val foo:Foo) extends AnyVal`. One of such methods may also return a companion object similarly to what Scala's standard collections `companion` method does. In other words, I don't get your real problem and what you shown looks like an example of [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – SergGr Dec 08 '17 at 01:00
  • I see. I'm writing library code the needs to be generic and relies on occasion on type classes such as above to automatically provide behavior for possible concrete implementations. none of the code you see above exists in reality I spent hours trying to pin down the current problem in my code and write a short dumbed down ```Foo``` ```Bar``` example. Which I now suspect hasn't been to my benefit at all :P – shayan Dec 08 '17 at 01:11
  • @shayan, this sound quite suspicious to me. IMHO typical type class in Scala is a generic trait that lists all methods, and implicit `val`s and/or `def`s that let you create instance(s) of that trait specific to the given type. And all you do in your type-class-aware code is use methods from that trait. So how come that `barSpecificAction` is not defined in such trait but you still want to call it from "outside"? – SergGr Dec 08 '17 at 01:25
  • Keen observation. it's really complicated to explain in text but this only occurs in 1 place in my code. I'm considering creating a question for it. the AST itself if omitting those ugly generated implicits for tuples (like the one you see in ```slick tuple shapes```) is about 100 lines of code + some lines of client code that reproduce the issue. would that be a good idea to post the entire thing on stackoverflow and ask what's wrong with it? – shayan Dec 08 '17 at 01:41
  • @shayan, 100 lines doesn't sound as too much. But what I believe is much more important is not just show your code to solve the problem - describe the actual problem you are trying to solve so that SO users might suggests higher-level solutions. – SergGr Dec 08 '17 at 01:44
  • Would you be kind enough to take a look at [my new question on the topic](https://stackoverflow.com/questions/47707034/implicit-resolution-of-fbounded-type) when you had time? – shayan Dec 08 '17 at 02:58
3

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.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • with drop of existential types in dotty does this mean a change to how it'll be compiled is it is? – shayan Dec 07 '17 at 23:58
  • In dotty 0.6.0-bin-20171207-63e491a-NIGHTLY in console `scala> val bar1 = new BroFinder1[Foo].brotherOf` produces `val bar1: Main.BroFinder1[Main.Foo]#b.Bro = Main$Bar@4657d37a` although `val bar1: BroFinder1[Foo]#b.Bro = new BroFinder1[Foo].brotherOf` doesn't compile. So existential types exist but you can't use them. Surely `val bf = new BroFinder1[Foo]; val bar1: bf.b.Bro = bf.brotherOf` compiles both in Dotty and Scala. – Dmytro Mitin Dec 08 '17 at 22:51