Search code examples
genericskotlinabstract-classcovariancecontravariance

Generics in/out for abstract container tool in Kotlin?


I am creating an abstract tool class that operates on another set of foreign classes (not controlled by me). The foreign classes are conceptually similar at certain points of interface, yet have different syntax for accessing their similar properties. They also have different syntax for applying the results of the tool's operations. I have created a data class with inner classes, based on this answer by @hotkey.

Here is the generics problem: The foreign classes are fundamentally containers of elements. Each class' container type is different. Certain of the containers have a fixed element type, while other containers carry a generic element type. I am having trouble applying the generics concepts of in vs. out, covariance vs. contravariance to this model. Here is a simplified example using slicing of CharSequence and List that parallels the problem almost exactly, with respect to the generics:

// *** DOES NOT COMPILE ***
data class Slicer<C,E>(val obj: C, val beg: Int, val end: Int) {
  // C is container type; E is element type
  // but unsure how to apply in/out properly
  inner abstract class SObj<C,E>{
    abstract val len: Int  // an input that tool requires
    abstract val sub: C    // an output of tool (container)
    abstract val one: E    // an output of tool (element)
    inner class TCsq(val c: CharSequence): SObj<C,E>() {
      override val len get()= c.length
      override val sub get()= c.substring(adjusted)  // PROBLEM
      override val one get()= c[finder+5]            // PROBLEM
    }
    inner class TList<E>(val l: List<E>): SObj<C,E>() {
      override val len get()= l.size
      override val sub get()= l.slice(adjusted)      // PROBLEM
      override val one get()= l[finder]              // PROBLEM
    }
  // sample ops use both data class vals and abstract properties
  val adjusted get()= (beg+1)..(len-1)
  val finder get()= (end-beg)/2
  }
}

How do I properly apply in/out here to make this work? Alternatively, if this isn't the best construct, how else might this be structured?

N.B. Keep in mind that CharSequence and List represent foreign classes which can't be modified, and adjusted and finder are samples of many operations the tool is performing on the classes. Today, the tool's operations are just littered about the code base within (or as extensions to) the various containers in a repetitive and non-uniform manner.


Solution

  • If I understood the question correctly, variance isn't relevant here, you just got the parameters wrong:

    inner class TCsq(val c: CharSequence): SObj<CharSequence, Char>()
    

    and

    inner class TList<E>(val l: List<E>): SObj<List<E>,E>()
    

    What you can't do this way is to have operations which "change E": this requires higher-kinded types which Kotlin doesn't support.