简体   繁体   中英

Kotlin: Making subclasses of generic types inherit functions and restricting the available types of output

I have the following Kotlin classes implementing something similar to the Collections interface or a set that contains multiple elements.

abstract class MyCollection<C: MyCollection<C>> {
    abstract fun contains(e: Member<C>): Member<CollectionOfBooleanValues>
    //Function that determines if a Member<C> is present in this collection.
    // Maps to a collection of boolean values (True, False and Unsure)

    abstract fun <E: MyCollection<E>> mapToOtherCollection(f: (Member<C>) -> Member<E>): E
    //Function that return a collection of the element that are created from
    //a mapping of this collection through a function f.
    //This could for example be the owners of the things of this collection, provided we
    //have a Persons class extends MyCollection. Or a collection of all colors of the things in this collection.
}

abstract class BigCollection<C: BigCollection<C>>: MyCollection<C>() {
    override fun contains(e: Member<C>): Member<CollectionOfBooleanValues> = Unsure

    abstract override fun <E: MyCollection<E>> mapToOtherCollection(f: (Member<C>) -> Member<E>): E
}

abstract class SmallCollection<C: SmallCollection<C>>(val choices: List<Member<C>>): BigCollection<C>() {
    override fun contains(e: Member<C>): Member<CollectionOfBooleanValues> =
        if(choices.contains(e))
            True
        else
            False
}

abstract class Persons<D: Persons<D>>: MyCollection<D>() {
    override fun contains(e: Member<D>): Member<CollectionOfBooleanValues> {
        return True
    }
}

abstract class Member<D: MyCollection<D>>(sym: String, val collectionType: D) {
}

object CollectionOfBooleanValues: SmallCollection<CollectionOfBooleanValues>(choices = listOf(True, False, Unsure)){
    override fun <E: MyCollection<E>> mapToOtherCollection(f: (Member<CollectionOfBooleanValues>) -> Member<E>): E =
            throw RuntimeException("Not implemented");
}

object True: Member<CollectionOfBooleanValues>("true", CollectionOfBooleanValues)
object False: Member<CollectionOfBooleanValues>("false", CollectionOfBooleanValues)
object Unsure: Member<CollectionOfBooleanValues>("unsure", CollectionOfBooleanValues)

So instances of one of MyCollection's subclasses describe some sort of set and instances of the class Member< C > describe a particular member of that collection. The subclasses BigCollection and SmallCollection determine what size of collection we have. My idea is to add more here depending on how hard the collections are to work with.

One could imagine the following examples

Red could be an instance/object of the type Member< Color >

Persons could be a subtype of the type SmallCollection< Persons >

Color could be a subtype of the type BigCollection< Color > and be essentially infinite. This means for example that the function contains(Member< Color >) can never return false, only Unsure or possibly True if it found it and throw an exception after a specific timeout.

Just so you have a basic idea of what I'm trying to do.

Notice how the JVM's type erasure forces me to use recursive generics if I want to have access to the type MyCollection< C > at both compile time and runtime.

We can see that it's fairly type safe. We know that mapToOtherDomain will return an E, which is a subtype of MyCollection< D > and it's going to be the same type as the E in the function (Member< C >) -> Member< E >) we put in.

Now, what would be neat would be if the subclasses of MyCollection could override the function mapToOtherCollection, so that the size is reflected in the static signature of the function. The function f is a one-to-one mapping so if we map a smallCollection of type A to BI want the output to be a smallCollection of B.

I feel like this should be possible, especially since it's almost possible in Java, and Kotlin is supposed to be an extension on Java's generic type system. I can't get it to work though. I want it to override, but still restrict the return type (and thus also the input type)

I've played around with method signatures like this:

abstract class BigCollection<C: BigCollection<C>>: MyCollection<C>() {
    abstract override fun <E: BigCollection<E>> mapToOtherCollection(f: (Member<C>) -> Member<E>): E
}

which gives the compiler error that it doesn't override the previous method, and like this

abstract class MyCollection<C: MyCollection<C>> {
    abstract fun <E: MyCollection<E>> mapToOtherCollection(f: (Member<C>) -> Member<E>): MyCollection<E>
}

abstract class BigCollection<C: BigCollection<C>>: MyCollection<C>() {
    abstract override fun <E: MyCollection<E>> mapToOtherCollection(f: (Member<C>) -> Member<E>): BigCollection<E>
}

which says The Type argument is out of bounds.

How can I just restrict the in/out types to better match the subclass of the generic class in this example? It would also be neat if the compiler knew that the output of contains() in a BigCollection is going to be Unsure statically rather that dynamically.

Thanks

It's hard to tell whether this would help or not, but you could at least consider this as a workaround:

abstract class BigCollection<C: BigCollection<C>>: MyCollection<C>() {
    override fun <E: MyCollection<E>> mapToOtherCollection(f: (Member<C>) -> Member<E>): MyCollection<E> {
        return this.mapToOtherCollection(f)
    }

    abstract fun <E: BigCollection<E>> mapToOtherCollection(f: (Member<C>) -> Member<E>): BigCollection<E>
}

What happens here is similar to JVM "bridge" methods: we create a method with a more specific signature and delegate to it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM