Issue with mapTo() on a sequence

Hi,

when writing code like

fun main() {
    val a = mutableListOf<Int>()
    val b = setOf(1, 2, 3).asSequence().mapTo(a) { it * 2 }.find { it == 4 }
    println(a)
    println(b)
}

I would have assumed that printing a would result in only [2, 4] whereas it actually results in [2, 4, 6]. Why is that? Isn’t the whole point a sequence to only evaluate those mappings that are needed to make find return true?

Whatever the answer is, any hints on how to actually get the behavior that only those mappings land in a until find succeeds?

Thanks in advance for any insights!

mapTo() is a terminal operation; it forces the complete sequence to be evaluated, and then returns the destination — the list a, which then gets scanned in the usual way.

Normally in this sort of case I’d expect to see a simple map() (which is an intermediate operation, i.e. not terminal).⠀I’m guessing you’re using mapTo() in order to see the sequence behaviour — but unfortunately, it changes that behaviour!

One way to see how the sequence behaves would be to use a straight map() (ignoring a completely), and add side-effects in the lambdas, e.g.:

val b = setOf(1, 2, 3).asSequence()
        .map{ println("Doubling $it"); it * 2 }
        .find{ println("Checking $it"); it == 4 }
println("Result: $b")

This prints:

Doubling 1
Checking 2
Doubling 2
Checking 4
Result: 4

This shows that only the first two elements of the set get evaluated, doubled, and checked — so the sequence is behaving lazily, as expected.

1 Like

But from an API design point of view, what’s the reason for mapTo() being terminal, but map() not? To me that design seems counter-intuitive, as the only difference between those functions is the way the target collection is specified.

I’m not only using mapTo() here for demonstration purposes, but because I’m really interested in all mapped values until a specific value was found in some code that follows this block, and I really do not want to map unnecessary original values, as in my case the mapping is an expensive operation.

Because with mapTo you ask “transform this and put this in the collection and after filter” and with map only “transform this and filters”

You can emulate the behaviour that you want with this:


fun main() {
    val a = mutableListOf<Int>()
    val b = setOf(1, 2, 3).asSequence().map { it * 2 }.onEach{ a.add(it) }. find { it == 4 }
    println(a)
    println(b)
}

2 Likes

Thanks, finally a good use for onEach() :slight_smile:

2 Likes