Michael de Kaste
11/23/2021, 11:06 AMinterface OpenRange<T : Comparable<T>> {
val start: T?
val endInclusive: T?
operator fun contains(value: T): Boolean = (
start?.let { value >= it }
?: true
) && (endInclusive?.let { value <= it } ?: true)
fun isEmpty(): Boolean = start != null && endInclusive != null && (start!! > endInclusive!!)
}
and then on something like
operator fun LocalDateTime.rangeTo(other: LocalDateTime?) = object : OpenRange<LocalDateTime> {
override val start: LocalDateTime = this@rangeTo
override val endInclusive: LocalDateTime? = other
}
But ofcourse, LocalDateTime already has an rangeTo, but yet, this compiles without trouble.
But when I call a localdatetime with the '..' operator now with a nullable other time, I get a type mismatch like the following.Michael de Kaste
11/23/2021, 11:08 AMephemient
11/23/2021, 11:24 AMephemient
11/23/2021, 11:27 AMoperator fun <T : Comparable<T>> T?.rangeTo(other: T?): OpenRange<T>
println(null..null)
works. I suppose it defaults to T: Nothing
, and Nothing : Comparable<Nothing>
.Michael de Kaste
11/23/2021, 11:40 AMMichael de Kaste
11/23/2021, 11:41 AMephemient
11/23/2021, 11:53 AMfun main(args: Array<String>)
because arg-less main
was introduced in 1.3) though, so presumably there's something else going on with your codeephemient
11/23/2021, 11:53 AMMichael de Kaste
11/23/2021, 11:55 AMMichael de Kaste
11/23/2021, 11:55 AMKlitos Kyriacou
11/23/2021, 12:02 PMephemient
11/23/2021, 12:04 PMephemient
11/23/2021, 12:07 PMString(CharArray(Int.MAX_VALUE) { '\uffff' })
is probably the maximal value, but that's not practically representable in memoryMichael de Kaste
11/23/2021, 1:21 PMIt's the maximum value
and There is no endtime
. It also makes the check and usages of the date itself typesafe. Knowing that the endtime is absent can give you additional information you might lose by just defaulting to max.
we'd like to never abstract away informationMichael de Kaste
11/23/2021, 1:22 PMtime in start..(end ?: SpecificTypeEndValueDependingOnType)
is a manual mess, especially if you need to do it in ALOT of situationsMichael de Kaste
11/23/2021, 1:24 PMtime in (start ?: MinValue)..(end ?: MaxValue)
ephemient
11/23/2021, 1:34 PMRangeBoundedAbove
and RangeBoundedBelow
, with a common supertype, but annoyingly ClosedRange
can't be made to implement another interface so you'd have to create a type to represent a range bounded on both endsephemient
11/23/2021, 1:42 PMobject Singleton : Comparable<Singleton> { override fun compareTo(other: Singleton): Int = 0 }
interface Range<T> {
val start: T?
val endInclusive: T?
fun isEmpty(): Boolean
fun contains(value: T): Boolean
}
interface NonEmptyRange<T> : Range<T> {
override fun isEmpty(): Boolean = false
}
private object EmptyRange : Range<Nothing> {
override val start: Nothing? get() = null
override val endInclusive: Nothing? get() = null
override fun isEmpty(): Boolean = true
override fun contains(value: Nothing): Boolean = false
}
private object AllRange : NonEmptyRange<Nothing> {
override val start: Nothing? get() = null
override val endInclusive: Nothing? get() = null
override fun contains(value: Nothing): Boolean = true
}
class RangeBoundedBelow<T : Comparable<T>>(override val start: T) : NonEmptyRange<T> {
override val endInclusive: T? get() = null
override fun contains(value: T): Boolean = start <= value
}
class RangeBoundedAbove<T : Comparable<T>>(override val endInclusive: T) : NonEmptyRange<T> {
override val start: T? get() = null
override fun contains(value: T): Boolean = endInclusive >= value
}
class RangeBounded<T : Comparable<T>>(override val start: T, override val endInclusive: T) : NonEmptyRange<T> {
init {
require(start <= endInclusive)
}
override fun contains(value: T): Boolean = start <= value && endInclusive >= value
}
fun <T> emptyRange(): Range<T> = EmptyRange as Range<T>
fun <T> allRange(): Range<T> = AllRange as Range<T>
operator fun <T : Comparable<T>> T.rangeTo(other: T): RangeBounded<T> = RangeBounded(this, other)
@JvmName("rangeToNullable")
operator fun <T : Comparable<T>> T.rangeTo(other: T?): NonEmptyRange<T> = if (other == null) RangeBoundedBelow(this) else RangeBounded(this, other)
@JvmName("nullableRangeTo")
operator fun <T : Comparable<T>> T?.rangeTo(other: T): NonEmptyRange<T> = if (this == null) RangeBoundedAbove(other) else RangeBounded(this, other)
Michael de Kaste
11/23/2021, 1:55 PMMichael de Kaste
11/23/2021, 2:27 PMval test: LocalDate? = null
val test2: LocalDate? = null
val test3 = test..test2
sadly this isn't possible with your example 😛ephemient
11/23/2021, 2:31 PM@JvmName("nullableRangeToNullable") operator fun <T : Comparable<T>> T?.rangeTo(other: T?): Range<T>
but you'd have to define what the behavior of null..null
will be; I excluded it as unclearMichael de Kaste
11/23/2021, 2:33 PMMichael de Kaste
11/23/2021, 2:33 PMephemient
11/23/2021, 2:34 PMtest..test2
case, you do have types. and also it seems that the compiler is happy to treat null..null
as Range<Nothing>
, from my experiment up-threadephemient
11/23/2021, 2:35 PMT?.rangeTo(T?)
function, it certainly can get null
on both sides. but what should that even mean?Michael de Kaste
11/23/2021, 2:38 PMephemient
11/23/2021, 2:47 PM@JvmName("nullableRangeToNullable")
operator fun <T : Comparable<T>> T?.rangeTo(other: T?): Range<T> = if (this == null) if (other == null) allRange() else this..other else this..other
if that's what you want it to meanMichael de Kaste
11/23/2021, 3:04 PMoperator fun <T : Comparable<T>> T?.rangeTo(other: T?): Range<T> = when {
this == null && other != null -> RangeBoundedAbove(other)
this != null && other == null -> RangeBoundedBelow(this)
this != null && other != null -> RangeBounded(this, other)
else -> allRange()
}
I also like the inclusion of an emptyRange represented with nulls, even though I have no usecase for it yetStephan Schroeder
11/24/2021, 7:55 AMnull
useful, so the dev is used to check up on what the meaning of null for every struct is, but in Kotlin null
is assumed to be the abscence of a value. Breaking that expectation is a code smell in my book. So I'd wrap the value in a sealed class to handle these special values explicitly:
sealed class Value<T: Comparable<T>>
class Finite<T: Comparable<T>>(val value: T): Value<T>()
object NegInfinity: Value<Nothing>()
object PosInfinity: Value<Nothing>()
interface OpenRange<T, V : Value<T>> where T:Comparable<T>, T: Any {
val start: V
val endInclusive: V
// ...
}
And suddenly you can even discern between positive and negative infinitiy.ephemient
11/24/2021, 8:24 AMephemient
11/24/2021, 8:41 AMsealed class WithLimits<T : Comparable<T>> : Comparable<WithLimits<T>>
operator fun <T : Comparable<T>> ClosedRange<WithLimits<T>>.contains(value: T)
reusing the built-in Finite(x)..Finite(y)
and just enhancing in
Michael de Kaste
11/24/2021, 9:13 AMMichael de Kaste
11/24/2021, 10:43 AMStephan Schroeder
11/24/2021, 1:24 PM!!
fun isEmpty(): Boolean {
val startPoint = start ?: return true
val endPoint = endInclusive ?: return true
return startPoint > endPoint
}