https://kotlinlang.org logo
#getting-started
Title
# getting-started
c

CLOVIS

10/19/2023, 1:32 PM
I guess this is expected behavior, but it surprised me:
Copy code
val found = sequenceOf(1, 2, 3, 4, 5)
    .filter { it % 2 == 0 }
    .onEach { println("Remaining: $it") }
    .any()
        
println(found)
I was expecting
Remaining: 2
to appear, but no, the
onEach
is never called. It seems that
any
is able to know that there will be other elements without actually processing them. Playground
🤯 3
j

Joffrey

10/19/2023, 1:35 PM
> It seems that any is able to know that there will be other elements without actually processing them Yes, this is because you hardcode the sequence as a fixed-length sequence. If you replace
sequenceOf()
by a
sequence { ... }
builder, it will print
Remaining: 2
(EDIT: wrong) https://pl.kotl.in/3iXcnj8sh
c

CLOVIS

10/19/2023, 1:36 PM
No, it's because it's
any
. Replace
any
by
first
in my example, and you will see that the
onEach
prints.
j

Joffrey

10/19/2023, 1:36 PM
Well, I wouldn't say no. It's because it's
any
AND because it's a fixed-length sequence (EDIT: wrong)
c

CLOVIS

10/19/2023, 1:36 PM
(and thanks to the
filter
, my sequence is not fixed-length)
Replace
first
by
any
in your example, and you will see it doesn't print either
j

Joffrey

10/19/2023, 1:37 PM
Oh sorry I thought I had. I tried with both
first
and
any
in your initial code, before trying the dynamic sequence, and I forgot to revert back to
any
in my attempt 😅
c

CLOVIS

10/19/2023, 1:38 PM
It's because
any
is implemented with
iterator().hasNext()
, and the iterator returned by
onEach
just delegates its
hasNext
to the previous layer, thus
any
is able to ask the filter directly if there are any elements, without actually processing them
👍 1
j

Joffrey

10/19/2023, 1:38 PM
Yep that makes sense
It's still surprising, but not too far-fetched
c

CLOVIS

10/19/2023, 1:39 PM
Still, I was expecting
any
and
first
to have the exact same side effects
1
j

Joffrey

10/19/2023, 1:40 PM
I can understand that it's not the case, though. The intent is somewhat different between
any()
and
first()
.
any()
is more like asking a question about the state of the sequence, not really about processing items, which is the case for
first()
.
y

Youssef Shoaib [MOD]

10/19/2023, 1:53 PM
I think it's because
first
needs to actually get an item out, while
any
merely needs to know that one is available. Theoretically, you could have a (weird) onEach implementation that runs its block in
hasNext()
and not
next()
, but that'd be pointless in normal cases except in this example
👌 3
j

Joffrey

10/19/2023, 1:54 PM
> I would expect this ... To be semantically the same as ... No,
onEach
should only occur on items that are not filtered out, you can't run the
onEach
code inside the filter lambda, it's not equivalent
🙈 1
y

Youssef Shoaib [MOD]

10/19/2023, 1:55 PM
You can imagine that a filter + map maybe shouldn't produce an element if one is not required, and so
any()
should only need to know if the filter succeeds, and it doesn't care that the mapping should actually take place and produce a result (although maybe there's an issue here with exceptions)
In fact, this code demonstrates this;
Copy code
fun main() {
    val found = sequenceOf(1, 2, 3, 4, 5)
        .filter { it % 2 == 0 }
        .map { TODO() }
        .onEach { println("Remaining: $it") }
        .any()
      
    println(found)
}
This prints
true
, and I think I agree with that result, although I can see an argument that it should result in an exception instead. I think due to the laziness of sequences it makes sense (in a Haskell-y way) that a sequence doesn't care about side effects until they absolutely have to run.
k

Klitos Kyriacou

10/19/2023, 2:00 PM
I found it interesting that it's impossible to do the equivalent in Java:
Copy code
var found = IntStream.of(1, 2, 3, 4, 5)
        .filter(n -> n % 2 == 0)
        .peek(n -> System.out.printf("Remaining: %s%n", n))
        .anyMatch(x -> true);
This prints "Remaining: 2" because the element has to be examined and passed to the predicate of
anyMatch
. Java doesn't have the equivalent of Kotlin's
any()
.
y

Youssef Shoaib [MOD]

10/19/2023, 2:02 PM
Maybe the
Sequence.any
documentation should have a note that
any()
is not equivalent to
any { true }
because of side effects and exceptions
💯 2
j

Joffrey

10/19/2023, 2:04 PM
That's a very good point. While Java doesn't have the equivalent for Kotlin's
any()
, Kotlin does have an equivalent to Java's
anyMatch(e -> true)
(and that is
any { true }
). So you're right I think it should be clarified in the docs that
any()
is not the same as
any { true }
regarding side effects
s

Sam

10/19/2023, 2:34 PM
This is fascinating, I can't decide whether I think it's a bug or not 😮. The documentation is light on details, but it categorises sequence operations into only two groups, terminal and non-terminal, and so until reading this I would have assumed that all terminal operations had comparable behaviour...
2
j

Joffrey

10/19/2023, 2:35 PM
@CLOVIS I think you should consider opening an issue on YouTrack for the Kotlin team, so at least they can clarify whether this is considered expected behaviour or a bug. And if it's expected, update the docs accordingly
👍 2
c

CLOVIS

10/19/2023, 3:05 PM
2 Views