Learner
Learner

Reputation: 1313

Scala method call with generic arguments appears not polymorphic - what is wrong

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!

Upvotes: 3

Views: 785

Answers (2)

Travis Brown
Travis Brown

Reputation: 139038

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.

Upvotes: 1

stew
stew

Reputation: 11366

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.

Upvotes: 4

Related Questions