Feature #21520
Updated by nuzair46 (Nuzair Rasheed) 3 days ago
# Abstract
Add a #peek method to Enumerator::Lazy that allows observing each element in a lazy enumeration pipeline without modifying or consuming the stream.
# Background
Ruby provides Enumerator::Lazy for efficient lazy stream processing. However, unlike languages such as Java, it lacks a clean way to inspect intermediate elements during lazy evaluation.
Currently, developers must misuse .map for side effects, example:
```rb
(1..).lazy.map { |x| puts x; x }.select(&:even?).first(3)
```
This is semantically incorrect and confusing, since .map implies transformation, not observation.
# Proposal
Introduce Enumerator::Lazy#peek, which yields each item to a block and returns the item unmodified, similar to Object#tap, but in a lazy stream:
```rb
(1..).lazy
.peek { |x| puts "saw: #{x}" }
.select(&:even?)
.first(3)
```
This would be equivalent to:
```rb
lazy.map { |x| block.call(x); x }
```
but with improved semantic clarity.
# Use cases
• Debugging lazy enumerators without breaking the chain
• Logging or instrumentation in pipelines
• Educational / demo use for showing lazy evaluation step-by-step
• Cleaner replacement for map { puts x; x } hacks
Example:
```rb
data = (1..).lazy
.peek { |x| puts "got #{x}" }
.select(&:even?)
.first(5)
```
result:
```rb
got 1
got 2
got 3
got 4
got 5
...
got 10
```
And return [2, 4, 6, 8, 10]
# Discussion
#peek is a minimal, non-breaking addition that improves clarity and idiomatic usage of Enumerator::Lazy. It avoids abusing .map for observation and is familiar to developers from other languages. #peek is also not needed for other enumerators where .tap or .each can do the job.
It mirrors Java’s .stream().peek(...) and makes Ruby’s lazy enumeration more expressive and readable.
# See also
• [Java Stream.peek](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#peek-java.util.function.Consumer-)
I would be glad to work on this and make a PR. Thank you.