Generic Constraints: Lower Bound

In the Kotlin docs about Generic Constraints, it specifically mentions that it’s possible to create an upper bound for a generic, but it says nothing about lower bounds. Suppose I’m trying to implement an Optional:

sealed class Optional<T> {
    abstract fun or(other: Optional<T>): Optional<T>
    
    data class Present<T>(val value: T) : Optional<T>() {
        override fun or(other: Optional<T>) = this
    }
    object Absent : Optional<Nothing>() {
        override fun or(other: Optional<Nothing>) = other
    }
}

fun main() {
    println(Optional.Present(5).or(Optional.Present(3)))
}

Of course, this has several problems:

  • Absent can’t be used effectively, because T cannot become out T due to the input Optional<T>.
  • It’s impossible to use Optional.Present(5).or(Optional.Present(3.0)) to get an Optional<Number>

This requires the ability for the method or to take a type parameter T2 that has a lower-bound of T, so that if T is Int, T2 may be Int, Number, or Any.

It’s possible to achieve this with an extension method:

sealed class Optional<out T> {    
    data class Present<T>(val value: T) : Optional<T>()
    object Absent : Optional<Nothing>()
}

fun <T> Optional<T>.or(other: Optional<T>) = when (this) {
    is Optional.Present -> this
    Optional.Absent -> other
}

fun main() {
    println(Optional.Present(5).or(Optional.Present(3.0)))
    println(Optional.Present(5).or(Optional.Absent))
    println(Optional.Absent.or(Optional.Present(2)))
}

However, this would be impractical in any scenario that relies on extensibility, allowing users of the library to write their own implementations. (I only use Optional here as an easy example.)

How can I achieve the same generic allowances as the extension method, while maintaining the extensibility of an abstract function? To my knowledge, it is impossible in Java, but I’m hoping that Kotlin’s more advanced type system has a way.

2 Likes

I’m confused about this too, so unfortunately can’t give you a clean answer. But a couple points…

I think the docs do talk about lower bounds. Isn’t that <in Foo>, which corresponds to Java’s <? super Foo>? But, I don’t see how that helps you here. It seems like what you want is something that I don’t know how to do in Kotlin…

// not real code
abstract fun or(other: Option<U>): Option<common base of T and U>

Your example code:

Optional.Present(5).or(Optional.Present(3.0))

Is returning an Optional<Any> for me, not Optional<Number>. So I’m not sure that’s really working how you want. I see this by assigning it to something, putting the cursor over the val’s name, and going to the menus > View > Type Info.

14%20PM

1 Like

Wouldn’t this work, then?

abstract fun <S, U : S, T : S> or(other: Optional<U>): Optional<S>
1 Like

Oops, yeah it would. I tried it and it works with the extension function, but not with the abstract member function. Not sure why. ?

open class Base
class Sub1 : Base()
class Sub2 : Base()

fun main() {        
    Optional.Present(Sub1()).or(Optional.Present(Sub2()))
}

When using the member function, the IDE reports that whole expression as type Optional<Sub2>. With extension it reports Optional<Base> as expected.

Unfortunately, no, this method actually creates a function-local generic T that shadows the class-local generic T. It still works for the extension method because the function and class both must share the same generic T.

Of course you are right. Then your problem probably is not solvable in Kotlin currently. Though I would gladly see someone solve it.

What about where clause?

abstract fun <S, U : S> or(other: Optional<U>): Optional<S> where T : S

I’ve tried that, but I get a different error

[NAME_IN_CONSTRAINT_IS_NOT_A_TYPE_PARAMETER] T does not refer to a type parameter of or

I guess the problem is that you try to restrict S not T. While logically T is a subtype of S and S is a supertype of T is equivalent this does not work for where clauses. Apparently you can only restrict the type in front of the colon and here we try to restrict the type after it.
I don’t think this is possible in kotlin. This might be an interesting feature. I personaly can’t remember ever needing this, but then I might have subconciously worked around it because it is not possible (and I don’t think this is possible in java or C# either, which are the other 2 languages with similar generics that I use(d) a lot).

It is possible in Scala though. This is from the Scala stdlib’s Option type:

def orElse[B >: A](alternative: => Option[B]): Option[B]

Here [B >: A] indicates a type parameter B with a lower type bound (B must be a Supertype of A or the same type). The opposite and more common thing would be [B <: A] in Scala.

Horrible syntax though :zipper_mouth_face:

1 Like

Is this kind of problem could be solved with union type ?

abstract fun or(other: Optional<U>): Optional<T|U>
1 Like

Seams related to this other design discussion