https://kotlinlang.org logo
#codereview
Title
# codereview
m

Michael Böiers

09/29/2021, 11:17 AM
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

Mărcuţ Andrei

09/29/2021, 1:31 PM
Did you mean to
Copy code
fun IntRange.lazyToRange() = (this.start..this.endInclusive).asSequence()
and then to
Copy code
(1..10).lazyToRange()
?
p

Paul Griffith

09/29/2021, 2:26 PM
You could borrow from python and have a top-level
xrange(start: Int, stop: Int)
🤷
r

rook

09/29/2021, 5:30 PM
Don’t forget to infix so it can be as pretty as
Copy code
1 lazyToRange 10
😛
m

Michael Böiers

09/29/2021, 5:33 PM
@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

Mărcuţ Andrei

09/29/2021, 6:20 PM
🤔 it’s nice that you found what you looked for 😉
e

ephemient

09/29/2021, 6:44 PM
think smart 1
👀 1
m

Michael Böiers

09/29/2021, 7:09 PM
“Use them if lazyness matters”. What I said.
e

ephemient

09/29/2021, 7:15 PM
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

Michael Böiers

09/29/2021, 7:43 PM
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

Mărcuţ Andrei

09/29/2021, 7:55 PM
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

Michael Böiers

09/29/2021, 8:02 PM
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

ephemient

09/30/2021, 9:01 AM
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

Michael Böiers

09/30/2021, 9:06 AM
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

ephemient

09/30/2021, 9:10 AM
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

Michael Böiers

09/30/2021, 9:07 PM
Nice!
m

Mărcuţ Andrei

10/01/2021, 12:04 PM
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.
6 Views