Home > Software engineering >  Why is there no ClosedRange IN ClosedRange Operator in Kotlin?
Why is there no ClosedRange IN ClosedRange Operator in Kotlin?

Time:11-05

Let val a, b, x, y: LocalDate.

Then a..b in x..y is no valid expression: ClosedRange in ClosedRange is undefined.

However, (a..b).toRange() in (x..y).toRange() does work since Range in Range is defined.

Except when b < a or y < x since ranges can't be negative.

Please note that i'm developing for Android, which is where the .toRange() originates: androidx.core/core-ktx


3 Questions arise

  • Is this an oversight of the android / kotlin team?
  • Is this a deliberate decision? (e.g. because of some unintuitive behavior)
  • in case it's a deliberate decision: how to best circumvent it?

CodePudding user response:

Disclaimer: I'm not an Android developer and have no experience with androidx.core, so take my answer with a pinch of salt.

So assuming that you can do a..b to create a ClosedRange (which is something not available by default in the Kotlin stdlib, but maybe core-ktx defines its own extension for that), according to the Kotlin documentation, x in y gets translated to y.contains(x). Now, ClosedRange defines some contains methods, but none of them accepts another ClosedRange as a parameter, hence your error.

Now, apparently the Android stdlib defines its own concept of Range, which is unrelated to Kotlin's ClosedRange (as one does not extend the other). However, core-ktx defines a function to transform a ClosedRange to a Range, and that function is .toRange(). In addition to that, Range defines a contains(Range) method, hence why the second example compiles correctly.

So to summarise: Kotlin doesn't allow (1) to create ranges of dates by default, and (2) to check if one range is fully included in another range. However, you can easily overcome (1) by creating your own function to create a ClosedRange for dates (by creating a rangeTo extension function), and (2) by creating your own contains(Range) function.


Coming back to why a..b works when using core-ktx, I can't find a satisfactory answer. That library contains a rangeTo extension function to create a range out of any Comparable (and LocalDate is-a Comparable), but that function returns a Range, not a ClosedRange, so I'm not sure why a..b in x..y doesn't work in the first place.

CodePudding user response:

ClosedRange is part of the Kotlin standard library. Range is part of Android's SDK. These two libraries are made by different companies and were made with different goals in mind, so you can't necessarily call it an oversight. Maybe the Kotlin developers decided the meaning of a range being in another range might be ambiguous, and the Android developers didn't. Or maybe the Kotlin developers have a higher criteria for what functions are useful enough to include in the standard library.

One difference between the two classes is that the Android Range class forbids a range with the start value higher than the lower, but Kotlin ClosedRange allows it. This makes the concept of a ClosedRange containing another more ambiguous than with Ranges. What would it mean for a negative-direction ClosedRange to be in another ClosedRange? It's empty, but it has a span.

I'm not sure what you mean by circumventing it. You can define your own extension function for ClosedRange if you want, with behavior that depends on how you want to interpret the meaning of an empty range being contained.

operator fun <T: Comparable<T>> ClosedRange<in T>.contains(other: ClosedRange<out T>): Boolean =
    other.start in this && other.endInclusive in this

// or

operator fun <T: Comparable<T>> ClosedRange<in T>.contains(other: ClosedRange<out T>): Boolean =
    other.isEmpty() || (other.start in this && other.endInclusive in this)

// or

operator fun <T: Comparable<T>> ClosedRange<in T>.contains(other: ClosedRange<out T>): Boolean =
    (other.isEmpty() && (other.start in this || other.endInclusive in this)) || 
        (other.start in this && other.endInclusive in this)
  • Related