Project

General

Profile

Actions

Feature #21520

open

Feature Proposal: Enumerator::Lazy#tee

Feature #21520: Feature Proposal: Enumerator::Lazy#tee

Added by nuzair46 (Nuzair Rasheed) 3 months ago. Updated about 2 months ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:122847]

Description

Abstract

Add a #tee 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:

(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#tee, which yields each item to a block and returns the item unmodified, similar to Object#tap, but in a lazy stream:

(1..).lazy
     .tee { |x| puts "saw: #{x}" }
     .select(&:even?)
     .first(3)

This would be equivalent to:

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:

data = (1..).lazy
            .tee { |x| puts "got #{x}" }
            .select(&:even?)
            .first(5)

result:

got 1
got 2
got 3
got 4
got 5
...
got 10

And return [2, 4, 6, 8, 10]

Discussion

#tee 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. #tee 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

I have a draft PR for the implementation ready https://github.com/ruby/ruby/pull/14024

Updated by nuzair46 (Nuzair Rasheed) 3 months ago Actions #1

  • Description updated (diff)

Updated by Dan0042 (Daniel DeLorme) 3 months ago · Edited Actions #2 [ruby-core:122855]

#peek already exists, and does something different.

Updated by nuzair46 (Nuzair Rasheed) 3 months ago Actions #3 [ruby-core:122863]

Dan0042 (Daniel DeLorme) wrote in #note-2:

#peek already exists, and does something different.

Hey, Enumerator#peek exists. but Enumerator::Lazy#peek does not.
Enumerator#peek works differently than this suggestion.
So maybe the naming will be confusing.

Updated by nuzair46 (Nuzair Rasheed) 3 months ago Actions #4

  • Description updated (diff)

Updated by nuzair46 (Nuzair Rasheed) 3 months ago Actions #5 [ruby-core:122870]

nuzair46 (Nuzair Rasheed) wrote in #note-3:

Enumerator#peek works differently than this suggestion.
So maybe the naming will be confusing.

Running the whole CI, I see the collision with Enumerator#peek.
So I think the name should be changed to avoid confusion and collision.
Im thinking spy or tap_each

Updated by nobu (Nobuyoshi Nakada) 3 months ago Actions #6 [ruby-core:122880]

Or lazy_each?

Updated by nuzair46 (Nuzair Rasheed) 3 months ago Actions #7 [ruby-core:122882]

  • Subject changed from Feature Proposal: Enumerator::Lazy#peek to Feature Proposal: Enumerator::Lazy#lazy_each
  • Description updated (diff)

updated to lazy_each

Updated by nobu (Nobuyoshi Nakada) about 2 months ago Actions #9 [ruby-core:123002]

A couple days ago, another name came to me: tee.

Updated by nuzair46 (Nuzair Rasheed) about 2 months ago Actions #10 [ruby-core:123004]

nobu (Nobuyoshi Nakada) wrote in #note-9:

A couple days ago, another name came to me: tee.

Thanks, tee is a great fit. It’s short and feels very Unix-y. I will make changes to the PR and the proposal soon.

Updated by matz (Yukihiro Matsumoto) about 2 months ago Actions #11 [ruby-core:123008]

I don't think lazy_each is a good name. We just wanted to peek the element in the stream, being lazy or not. In that sense, tee is better, if you are familiar with UNIX (like me).

Matz.

Updated by nuzair46 (Nuzair Rasheed) about 2 months ago Actions #12

  • Subject changed from Feature Proposal: Enumerator::Lazy#lazy_each to Feature Proposal: Enumerator::Lazy#tee
  • Description updated (diff)

Updated by nuzair46 (Nuzair Rasheed) about 2 months ago Actions #13

  • Description updated (diff)

Updated by nuzair46 (Nuzair Rasheed) about 2 months ago Actions #14 [ruby-core:123012]

matz (Yukihiro Matsumoto) wrote in #note-11:

I don't think lazy_each is a good name. We just wanted to peek the element in the stream, being lazy or not. In that sense, tee is better, if you are familiar with UNIX (like me).

Matz.

Thanks Matz. I have updated the PR and proposal with the name #tee for the method.

Actions

Also available in: PDF Atom