https://kotlinlang.org logo
Title
r

Ruckus

03/17/2023, 4:46 PM
I have
sealed class Bounds<in T : Comparable<T>> {
    abstract operator fun contains(value: T): Boolean
}
And I want
object Unbounded : Bounds<Any?> {
    override fun contains(value: Any?) = true
}
However, I cannot as
Any?
violates the type bound. Is there a way to specify the type to make this work?
s

Sam

03/17/2023, 4:47 PM
Typically this is done with the
@UnsafeVariance
annotation — for example the built-in collections do this
r

Ruckus

03/17/2023, 4:48 PM
Unsafe variance is for violating
in
and
out
. My goal is to specify that
Unbounded
works for any comparable.
s

Sam

03/17/2023, 4:48 PM
You’re right, I misunderstood the question, sorry
y

Youssef Shoaib [MOD]

03/17/2023, 4:52 PM
Apparently this works:
sealed class Bounds<in T : Comparable<T>> {
    abstract operator fun contains(value: T): Boolean
}
object Unbounded : Bounds<Comparable<Comparable<*>>>() {
    override fun contains(value: Comparable<Comparable<*>>) = true
}
Not sure why the nested `Comparable`s are needed, but hey it runs at least
s

Sam

03/17/2023, 4:53 PM
Another solution is
class Unbounded<in T : Comparable<T>> : Bounds<T>() {
    override fun contains(value: T) = true
}
if you can live with it not being a singleton
y

Youssef Shoaib [MOD]

03/17/2023, 4:54 PM
I would've expected that
Comparable<*>
would be a subtype of
Comparable<T> where T: Comparable<T>
but I guess not?
This also works:
sealed class Bounds<in T : Comparable<T>> {
    abstract operator fun contains(value: T): Boolean
}
object Unbounded : Bounds<Comparable<Any?>>() {
    override fun contains(value: Comparable<Any?>) = true
}
Which I think (maybe?) makes more sense. Comparable's T is
in
, and so
Comparable<*>
is in reality
Comparable<Nothing>
, which I think then isn't a subtype of
Comparable<Comparable<Nothing>>
because it can only accept arguments of type
Nothing
. Hopefully that makes a bit more sense lol! Generic variance is truly
out
of this world...
r

Ruckus

03/17/2023, 5:29 PM
@Youssef Shoaib [MOD] Those get rid of the error, but they don't actually work:
interface B : Comparable<B>
class A(val spec: Bounds<B> = Unbounded)  // Error is now here
@Sam Yeah, that's what I'm currently using, but I was hoping for a singleton solution to match another part of the API more closely.
y

Youssef Shoaib [MOD]

03/17/2023, 5:44 PM
I think the easiest solution is this:
sealed class Bounds<in T : Comparable<T>> {
    abstract operator fun contains(value: T): Boolean
}

object Unbounded : Bounds<Comparable<Any?>>() {
    override fun contains(value: Comparable<Any?>) = true
}

fun <T: Comparable<T>> unbounded(): Bounds<T> = Unbounded as Bounds<T>

interface B : Comparable<B>

class A(val spec: Bounds<B> = unbounded())
r

Ruckus

03/17/2023, 6:29 PM
Indeed, that works. Feels a bit hacky, but may be the best option.
m

Michael de Kaste

03/27/2023, 11:28 AM
late response, but this looks a lot like my range custom range implementation that looks like this for 'unbounded' ranges:
sealed interface Range<T : Comparable<T>>{
    interface Open<T : Comparable<T>> : Range<T> {
        override fun contains(value: T) = true
    }
}

internal object OpenRange = Range.Open<Nothing>

fun <T : Comparable<T>> openRange() = Range.OPEN as Open<T>
its akin to how List<T> and emptyList() is used by kotlin self, you only have a cast problem, but you'll have to surpress that. Kotlin itself uses unsafevariance and stuff to surpress the cast warnings. The main takeaway is that unbounded should probably be Nothing, not Any?