Search code examples
kotlinnestedinner-classes

Type mismatch when using generics with abstract nested classes


I find an "Argument type mismatch" error in multiply function and don't know how to fix it. The code is:

abstract class Semigroup<T1> {
    abstract val one: SemigroupElement<T1>
    abstract fun builder(x: T1): SemigroupElement<T1>
    abstract val binOp: (SemigroupElement<T1>, SemigroupElement<T1>) -> SemigroupElement<T1>

    abstract inner class SemigroupElement<T1> {
        fun multiply(other: SemigroupElement<T1>) = binOp(this, other)  // Argument type mismatch
        abstract val value: T1
    }
}

Solution

  • When you name all type parameters the same (T1) it becomes confusing.

    Let's first rename the type parameter of class Semigroup from T1 to T and the type parameter of class SemigroupElement from T1 to E.

    Then you have the following code:

    abstract class Semigroup<T> {
        abstract val one : SemigroupElement<T>
        abstract fun builder(x: T): SemigroupElement<T>
        abstract val binOp: (SemigroupElement<T>, SemigroupElement<T>) -> SemigroupElement<T>
    
        abstract inner class SemigroupElement<E> {
            fun multiply(other: SemigroupElement<E>) = binOp(this, other)  // Argument type mismatch
            abstract val value: E
        }
    }
    

    And now it becomes clear why this doesn't work: The abstract binOp requires two SemigroupElement<T>, but you provide it with two SemigroupElement<E>. Since T and E are not related in any way the compiler protests with the error "Type mismatch."

    It's not clear what you want to achieve, but it looks like you may not even need a type parameter for SemigroupElement at all. Since SemigroupElement is an inner1 class of the (outer) class Semigroup it can only exist with an instance of Semigroup. Therefore you can access the outer class' type parameter T and let value be of that type:

    abstract class Semigroup<T> {
        abstract val one: SemigroupElement
        abstract fun builder(x: T): SemigroupElement
        abstract val binOp: (SemigroupElement, SemigroupElement) -> SemigroupElement
    
        abstract inner class SemigroupElement {
            fun multiply(other: SemigroupElement) = binOp(this, other)  // This compiles just fine now
            abstract val value: T
        }
    }
    

    1: Not nested, as your question title suggests; a nested class wouldn't have the inner modifier, allowing an instance of it even without an instance of the outer class. Here, you have an inner class, not a nested class.
    For more see the documentation about Nested and inner classes.