Sealed interface with inner classes that implements super interface and its super interface?

I’m wanting to know if it’s possible to declare a sealed interface, and to then declare a sealed class that implements that interface that “re-aliases” the properties of the interface so that they have more domain-specific names.

E.g. I am doing the Either pattern from functional programming, but I want to make a subclass that uses terms other than the functional programming “left” and “right” names. This is for convenience.

Here’s my current attempt:

// Either.kt
sealed interface Either<A, B> {
    open class Left<A, B>(open val left: A) : Either<A, B>
    open class Right<A, B>(open val right: B) : Either<A, B>
}
// ContentEither.kt
sealed interface ContentEither<T> : Either<Throwable, T> {
    data class Error<Throwable, T>(val error: Throwable) : Either.Left<Throwable, T>(error) {
        override val left: Throwable = this.error
    }
    data class Content<E, T>(val content: T) : Either.Right<E, T>(content) {
        override val right: T = this.content
    }
}

The problem is, Error is not known to implement ContentEither so then I get errors like this when I try to use it

Type mismatch.
Required:
ContentEither<T>
Found:
ContentEither.Content<???, T>

If I try to say that it does implement ContentEither, I then get this error:

sealed interface ContentEither<T> : Either<Throwable, T> {
    data class Error<Throwable, T>(val error: Throwable) : Either.Left<Throwable, T>(error), ContentEither<T>() {
        override val left: Throwable = this.error
    }
    data class Content<Throwable, T>(val content: T) : Either.Right<Throwable, T>(content), ContentEither<T> {
        override val right: T = this.content
    }
}
Type parameter A of 'Either' has inconsistent values: Throwable#1 (type parameter of org.me.ContentEither.Error), kotlin.Throwable

Is there a way to specify that these are the same Throwables? Is what I am trying to do possible in Kotlin?

In your code

    data class Error<Throwable, T>(val error: Throwable) : Either.Left<Throwable, T>(error), ContentEither<T>() {
//                   ^^^^^^^^^ this is not name of class, but name of type variable

Also, same references is stored twice (in base class and in derived), wasting some memory.

Correct code will be

sealed interface ContentEither<T> : Either<Throwable, T> {
    data class Error(val error: Throwable) : Either.Left<Throwable, Nothing>(error), ContentEither<Nothing> {
        override val left: Throwable get() = error
    }
    data class Content<T>(val content: T) : Either.Right<Throwable, T>(content), ContentEither<T> {
        override val right: T get() = this.content
    }
}
1 Like

Thanks!