0

I want to be able to pass a parameter of the same type in a function in the outer nested generic class, but also have a polymorphic internal generic class as well - allowing me to use an abstract base class/trait for InnerShell and OuterShell both. Is this even possible in Scala?

class OuterShellClass[Generic] {
  def doSomething(param : OuterShellClass[Generic]) : OuterShellClass[Generic] = this;
}

abstract class InnerShellClass[Type] {

}

class InnerShellSubclass[Type] extends InnerShellClass[Type]{

}
class wrapper{
  val obj : OuterShellClass[InnerShellClass[String]] = new OuterShellClass[InnerShellSubclass[String]];
}

If the OuterShellClass is a invariant, I get the error:

Note: InnerShellSubclass[String] <: InnerShellClass[String], but class OuterShellClass is invariant in type Generic

If the OuterShellClass is covariant, I get the error:

covariant type Generic occurs in contravariant position in type OuterShellClass[Generic] of value param

Update:

As I am thinking about it, this quick hack will work - please tell me there is a better solution. Since the generics inside OuterShellClass functions are effectively independent of the generic definition at class creation - not necessarily the same class - I cannot guarantee compatible classes at runtime. Move these specific functions to a global function class so I can reclaim the guarantee and get a solution. Do I really need to fragment my classes like this? Is there a better solution?

class OuterShellClass[+Generic] {
}

class Helper {
  def doSomething[T] (param1: OuterShellClass[T], param2: OuterShellClass[T]) =  param1;
}

abstract class InnerShellClass[Type] {

}

class InnerShellSubclass[Type] extends InnerShellClass[Type]{

}
class wrapper{
  val obj : OuterShellClass[InnerShellClass[String]] = new OuterShellClass[InnerShellSubclass[String]];
}
  • There is a good reason why that error exists. If there would not be an error and you could do what to did, then I could pick `obj` and call `doSomething` and pass to it a different sub class of `InnerShellClass` which would result in a runtime error. – Luis Miguel Mejía Suárez May 02 '21 at 03:11
  • I think it would be better to open a new question _(or edit this one)_ which is more focused in the meta problem you want to solve. – Luis Miguel Mejía Suárez May 02 '21 at 13:57

1 Answers1

-2

Code first - explanations after.

class OuterShellClass[+Generic] {
}

object OuterShellClass {
  def doSomething[T] (param1: OuterShellClass[T], param2: OuterShellClass[T]): OuterShellClass[T] =  param1
}

abstract class InnerShellClass[Type] {

}

class InnerShellSubclass[Type] extends InnerShellClass[Type]{

}
class wrapper{
  val obj : OuterShellClass[InnerShellClass[String]] = new OuterShellClass[InnerShellSubclass[String]]
  val result : OuterShellClass[InnerShellClass[String]] = OuterShellClass.doSomething(obj,new OuterShellClass[InnerShellSubclass[String]])
}

As I have been able to track down, Scala has a unique view on polymorphism connected to requiring all elements of an array belong to exactly the same class (with caveats). As a consequences, those from other languages (Java in particular) find polymorphism breaks expectations inside Scala generics, even when using covariance and contravariance. In this example, doSomething(ISubClass1 a, ISubClass2 b) is a compile error. Under polymorphism, it is legitimate to do this, but is not permitted inside Scala generics. The first version of this:

class OuterShellClass[+Generic] {
   def doSomething(param: OuterShellClass[Generic]): OuterShellClass[Generic] =  this
}

there is no requirement the class of param is the same as the class of the surrounding object in this case, causing the error requiring 'contravariance' which is impossible to meet in this context. The only way I have found to fix this is shift methods which reference the generic type (Generic in this case) and move them to a function in the companion object where these restrictions can be explicitly established - the object has no generics and the generics in the function are not covariance. This meets Scala requirements that containers have exactly one class of objects (with caveats) while still permitting use of some polymorphism in the inside generic class.

May your journey through Scala generics be smoother than my own...