3

Can't figure out why is this wrong or how to resolve it. Here's "distilled" code that reproduces the problem. Please help, but I'd appreciate none of the "why" questions - there are very real and valid answers to that but they are proprietary and unchangeable, thus irrelevant to the solution.

object Sandbox {

  // --- Following are really Java interfaces/classes ---
  trait R[X <: U[X,Y], Y <: E[X,Y]];
  class U[X <: U[X,Y], Y <: E[X,Y]] extends R[X,Y];
  class E[X <: U[X,Y], Y <: E[X,Y]] extends R[X,Y];

  trait R2 extends R[U2,E2];
  class U2 extends U[U2,E2] with R2;
  class E2 extends E[U2,E2] with R2;
  // --- End Java interfaces/classes ---

  def trouble[X <: U[X,Y], Y <: E[X,Y], Z <: R[X,Y]](r: Z) {}

  def main(args: Array[String]) {
    trouble(new U());  // Fine
    trouble(new E());  // Fine
    trouble(new U2()); // Not fine, reports:
    /*
     * inferred type arguments [Nothing,Nothing,Sandbox.U2]
     * do not conform to method trouble's type parameter bounds
     * [X <: Sandbox.U[X,Y],Y <: Sandbox.E[X,Y],Z <: Sandbox.R[X,Y]]
     */


    trouble(new E2()); // Not fine, reports:
    /*
     * inferred type arguments [Nothing,Nothing,Sandbox.E2]
     * do not conform to method trouble's type parameter bounds
     * [X <: Sandbox.U[X,Y],Y <: Sandbox.E[X,Y],Z <: Sandbox.R[X,Y]]
     */

    trouble[U2,E2,R2](new U2()); // Fine
    trouble[U2,E2,R2](new E2()); // Fine
  }
}

The compiler can't seem to infer X, Y and Z type args of the "trouble" method just based on the single argument specified. I understand that much - when I specify the types, it is OK, but it is very cumbersome. Is there a way to nudge/help the compiler in some way such that this stops being a problem?

Maybe I am placing too much confidence in Scala's type inference system, but all the information is available to it.

Thanks in advance!

Learner
  • 1,015
  • 9
  • 23

2 Answers2

4

You are placing too much confidence in Scala's type system inference. The more you try to work with these more complex (and especially recursive) type definition, the more you'll discover this. I don't know if I can offer a "why can't it figure this out" but I can offer something that works:

Don't parameterize the R on the types, but make them abstract members which must be declared in subtypes:

trait R {
    type X <: U[X,Y]
    type Y <: E[X,Y]
}

class U[X0 <: U[X0,Y0],Y0 <: E[X0,Y0]] extends R {
    type X = X0
    type Y = Y0
}
class E[X0 <: U[X0,Y0], Y0 <: E[X0,Y0]] extends R {
    type X = X0
    type Y = Y0
}

trait R2 extends R;
class U2 extends U[U2,E2] with R2
class E2 extends E[U2,E2] with R2

def trouble[X <: U[X,Y], Y <: E[X,Y], Z <: R](r: Z) {}

Then I believe you'll find your main method compiling unchanged.

As an aside, every semicolon in your code could be removed without changing the meaning.

stew
  • 10,996
  • 31
  • 48
  • 1
    Yes, I know about semicolons. I am just too used to them. Trouble is that R, U, E and R2, U2 and E2 are in Java really (can't change that fact) and the rest is in Scala. I have a slight chance of making small modifications to that Java code, but if I remove type parameters from R there all hell will break lose in that Java world. – Learner Dec 07 '12 at 12:43
  • The problem with this approach is that the `X` and `Y` type parameters on `trouble` are meaningless (and potentially misleading). They aren't linked in any way to `R`'s type members and will be inferred to be `Nothing`. – Travis Brown Dec 07 '12 at 19:08
1

See this answer (and the answers I've linked there) for a discussion of the limitations of Scala's type inference that are making a mess of things here.

If you don't need X and Y in the body (or return type) of trouble, you can use existential types to avoid referring to them at all:

def trouble[Z <: R[_, _]](r: Z) {}

If you do need them, you can use a view bound:

def trouble[X <: U[X, Y], Y <: E[X, Y], Z <% R[X, Y]](r: Z) {}

See the answers linked above for an explanation of why this works.

Community
  • 1
  • 1
Travis Brown
  • 135,682
  • 12
  • 352
  • 654