Is there any way to get lazy int ranges in Kotlin?...
# codereview
m
Is there any way to get lazy int ranges in Kotlin? I’m currently writing code like
(1..n).asSequence()
and it’s really annoying that you always have to clutter the code with the
asSequence()
call to make it lazy (read: perform well). EDIT: Yes, that came across the wrong way … lazy evaluation isn’t always better. 🙂
I mean, one could build something like
Copy code
fun Int.untilIncluding(t: Int) = (this..t).asSequence()
but
1.untilIncluding(n)
is still a mouthful compared to
1..n
Renamed it to
fun Int.lazyRangeTo(t: Int) = (this..t).asSequence()
, that’s actually not bad. Now if there only was syntactical support for it. Something like
(1…n)
😉
m
Did you mean to
Copy code
fun IntRange.lazyToRange() = (this.start..this.endInclusive).asSequence()
and then to
Copy code
(1..10).lazyToRange()
?
p
You could borrow from python and have a top-level
xrange(start: Int, stop: Int)
🤷
r
Don’t forget to infix so it can be as pretty as
Copy code
1 lazyToRange 10
😛
m
@Mărcuţ Andrei no, I meant to replace the .. operator, like Ben showed. 😊
😉 1
Something like this:
Copy code
infix fun Int.seqTo(to: Int) = (this..to).asSequence()
infix fun Int.seqUntil(to: Int) = (this until to).asSequence()

(1 seqTo 10).forEach { println(it) }
m
🤔 it’s nice that you found what you looked for 😉
e
think smart 1
👀 1
m
“Use them if lazyness matters”. What I said.
e
that's not how I read your initial statement "you always have to clutter the code with the asSequence() call to make it lazy", but ok
m
Well, if you need to make it lazy then you have to clutter the code. At least that’s what I intended to say.
Here’s an example from an advent of code challenge btw, where the laziness changes the runtime from days to milliseconds. 🙂
Copy code
private fun String.reactOne() = when {
    length < 2 -> this
    else -> 0.lazyRangeTo(length - 2)
        .filter { this[it] == this[it + 1].otherCase() }
        .map { withoutSubstring(it, 2) }
        .firstOrNull() ?: this
}
m
That only means the conclusions in the above article are accurate. Sequences could lead to over-engineering in most cases. Don’t abuse them. It’s nice to see the powerful syntax flexibility of Kotlin that enables us to use the language in our own way ☺️
m
So lazy evaluation is evil? Don’t tell Venkat 😉
I’m aware of the different arguments here. Yes, I think that lazy evaluation can be abused. In some cases it can lead to “clever” code which depends on the laziness too much, in a non-obvious way. Still, I think that my code snippet is not so terribly abusive. Strikes a good balance between performance and readability, in my humble opinion.
The consequence of avoiding (lazy) sequences in this use case would be imperative code. That would be both good and bad … it would perform even better (good), but it would be less readable (bad).
e
thinking about it some more… IntRange is already lazy, it doesn't materialize the whole list of values, it doesn't even check that it has reasonable endpoints (there are almost as many distinguishable empty ranges as there are non-empty ranges).
but because it is Iterable, many readily available operations are eager.
asSequence() is typically how to spell "from this iterator or iterable, subsequent operations should be lazy"; you'd like a more concise way of expressing that?
m
Yes. I think my suggested seqTo extension function is already sufficient. Having specific syntax for it would be nice, but maybe also non-obvious.
e
as a local extension, perhaps something like
Copy code
object RangeToSequence {
    operator fun Int.rangeTo(Int): Sequence<Int>
    // etc.
}
inline fun <T> seq(builder: RangeToSequence.() -> T): T
seq { 1..n }.map {}.filter {}.etc()
could look neat. but maybe too much extra relative to a simple infix fun.
m
Nice!
m
But it’s not worth to do it unless the iteration haves a chance at finishing earlier. Otherwise it’s a misleading syntactic sugar which only enables the least favorable performance on the most of the subsequent operations.