Reputation: 5371
I found interesting thing when I write generic sealed class. Here is first version:
// sample interface and implementation
interface MyInterface
class MyInterfaceImpl : MyInterface
sealed class Response<T: MyInterface>
data class Success<T: MyInterface>(val payload: T) : Response<T>()
data class Failure(val errorCode: Int) : Response<MyInterface>()
object Cancelled : Response<MyInterface>()
let's say we also have request function like this:
fun <T: MyInterface> requestObject(cls : KClass<T>): Response<T> = TODO("Request")
now at the use side we have errors:
fun test() = when (val response = requestObject(MyInterfaceImpl::class)) {
is Success -> print("Payload is ${response.payload}") // Smart cast perfectly works!
is Failure -> print("Error code ${response.errorCode}") // Incomparable types: Failure and Response<MyInterfaceImpl>
Cancelled -> print("Request cancelled") // Incomparable types: Cancelled and Response<MyInterfaceImpl>
}
First question:
Failure
andCancelled
isn't useT
for in/out positions, why this cast is unchecked and I need to suppress it?
After a while, Konstantin show me solution how to declare type safe sealed class:
sealed class Response<out T: MyInterface> // <-- out modifier here
data class Success<T: MyInterface>(val payload: T) : Response<T>()
data class Failure(val errorCode: Int) : Response<Nothing>() // <-- Nothing as type argument
object Cancelled : Response<Nothing>() // <-- Nothing as type argument
This declaration works like a charm and now I have questions:
Second question: why it's necessary to write
out
modifier here?Third question: why
Producer<Nothing>
is subtype ofProducer<MyInterface>
? By definition of covariant:Producer<A>
is subtype ofProducer<B>
ifA
subtype ofB
, butNothing
isn't subtype ofMyInterface
. It looks like undocumented extralinguistic feature.
Upvotes: 2
Views: 1447
Reputation: 39853
The variance doesn't work out in the end. Response<MyInterfaceImpl>
is not Response<MyInterface>
and therefore Failure
and Cancelled
cannot be used. Eventhough you're not using the gerneric type, you still declare it.
When putting out T
, you'll have an effect like ? extends T
in Java.
Then for Nothing
you have:
Nothing has no instances. You can use Nothing to represent "a value that never exists".
This also means it's a subtype of everything and thus also works for the generics.
Upvotes: 2