Search code examples
scalauser-defined-functionsgeneric-programming

Scala. Generic class declaration trouble


I have a trouble with class declaration in Scala:

   class Class2[
      A,
      B <: Class2[A,B,C],
      C <: Class3[A,C]
   ]


   class Class3[
      A,
      C <: Class3[A,C]
   ]

   class Class1[
      A, 
      B <: Class2[A,B,C], 
      C <: Class3[A,C]
    ](obj : B) { ... }

It is correct declaration, but every time I want to create an instance of that class I need to specify parameters A and C by hand. Like:

val X = Class1[Type1, Type2, Type3](correct_object_of_Type2)

If I try val X = Class1(OBJ) it will lead to error ...types [Nothing, B, Nothing] do not conform to [A, B, C]...

Why doesn't Scala infer types A and C from B? And how to declare class for a Scala compiler so that it will be able to specify A, C by itself? Thanks

EDIT

I'm sorry for formulation, the original task is to correctly define Class1 like:

   class Class2[
      A,
      B <: Class2[A,B,C],
      C <: Class3[A,C]
   ]


   class Class3[
      A,
      C <: Class2[A,C]
   ]

   ??class Class1(obj : Class2) { ... }??

... so that it will be correct to call val x = Class1(obj), where obj: Class2. When I try to define it as above, I'm getting error Class2 takes type parameters. Any ideas?

Sorry for inaccuracy.


Solution

  • It's possible to have the type parameters inferred by encoding the constraints as implicit parameters:

    class Class2[X, Y, Z]
    class Class3[X, y]
    
    class Class1[A, B, C](obj: B)(implicit
      evB: B <:< Class2[A, B, C],
      evC: C <:< Class3[A, C]
    )
    

    And then:

    scala> class Foo extends Class3[String, Foo]
    defined class Foo
    
    scala> class Bar extends Class2[String, Bar, Foo]
    defined class Bar
    
    scala> new Class1(new Bar)
    res0: Class1[String,Bar,Foo] = Class1@ff5b51f
    

    If you need to use instances of B or C as Class2[A, B, C] or Class3[A, C] in the definition of Class1, you can apply the appropriate evidence parameter (evB or evC) to them.

    Your version doesn't work because Scala's type inference system is very limited. It will first solve for A and end up at Nothing, since the argument to the constructor doesn't have an A in it. Next it will try to solve for B and not be able to find a value that satisfies the constraint, since it's already decided A is Nothing.

    So you have to decide whether type inference is worth an extra bit of complexity and runtime overhead. Sometimes it is, but in my experience when you have classes like this with relationships that are already fairly complex, it's usually not.