From 7163d4e14b000073efcd2211886b7d55969930ba Mon Sep 17 00:00:00 2001 From: Nuzair46 Date: Thu, 24 Jul 2025 13:44:21 +0900 Subject: [PATCH 1/3] Implements [ruby-core:122847] Enumerator::Lazy#peek --- enumerator.c | 47 +++++++++++++++++++++++++++++++ test/ruby/test_lazy_enumerator.rb | 21 +++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/enumerator.c b/enumerator.c index 3b69778f07..bc3ab25d16 100644 --- a/enumerator.c +++ b/enumerator.c @@ -2775,6 +2775,52 @@ lazy_with_index(int argc, VALUE *argv, VALUE obj) return lazy_add_method(obj, 0, 0, memo, rb_ary_new_from_values(1, &memo), &lazy_with_index_funcs); } +static struct MEMO * +lazy_peek_proc(VALUE proc_entry, struct MEMO *result, VALUE memos, long memo_index) +{ + struct proc_entry *entry = proc_entry_ptr(proc_entry); + + rb_proc_call_with_block(entry->proc, 1, &result->memo_value, Qnil); + + return result; +} + +static const lazyenum_funcs lazy_peek_funcs = { + lazy_peek_proc, 0, +}; + +/* + * call-seq: + * lazy.peek { |item| ... } -> lazy_enumerator + * + * Passes each element through to the block for side effects only, + * without modifying the element or affecting the enumeration. + * Returns a new lazy enumerator. + * + * This is useful for debugging or logging inside lazy chains, + * without breaking laziness or misusing +map+. + * + * (1..).lazy + * .peek { |x| puts "got #{x}" } + * .select(&:even?) + * .first(3) + * # prints: got 1, got 2, ..., got 6 + * # returns: [2, 4, 6] + * + * Similar in intent to Java's Stream#peek. + */ + +static VALUE +lazy_peek(VALUE obj) +{ + if (!rb_block_given_p()) + { + rb_raise(rb_eArgError, "tried to call lazy peek without a block"); + } + + return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_peek_funcs); +} + #if 0 /* for RDoc */ /* @@ -4624,6 +4670,7 @@ InitVM_Enumerator(void) rb_define_method(rb_cLazy, "uniq", lazy_uniq, 0); rb_define_method(rb_cLazy, "compact", lazy_compact, 0); rb_define_method(rb_cLazy, "with_index", lazy_with_index, -1); + rb_define_method(rb_cLazy, "peek", lazy_peek, 0); lazy_use_super_method = rb_hash_new_with_size(18); rb_hash_aset(lazy_use_super_method, sym("map"), sym("_enumerable_map")); diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb index 4dddbab50c..21ded2c60c 100644 --- a/test/ruby/test_lazy_enumerator.rb +++ b/test/ruby/test_lazy_enumerator.rb @@ -608,7 +608,7 @@ EOS end def test_require_block - %i[select reject drop_while take_while map flat_map].each do |method| + %i[select reject drop_while take_while map flat_map peek].each do |method| assert_raise(ArgumentError){ [].lazy.send(method) } end end @@ -715,4 +715,23 @@ EOS def test_with_index_size assert_equal(3, Enumerator::Lazy.new([1, 2, 3], 3){|y, v| y << v}.with_index.size) end + + def test_lazy_peek + out = [] + + e = (1..Float::INFINITY).lazy + .peek { |x| out << x } + .select(&:even?) + .first(5) + + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], out) + assert_equal([2, 4, 6, 8, 10], e) + end + + def test_lazy_peek_is_not_intrusive + s = Step.new(1..3) + + assert_equal(2, s.lazy.peek { |x| x }.map { |x| x * 2 }.first) + assert_equal(1, s.current) + end end From 7cf61aa6e91754387d8af1ee0cb42537b0a4243e Mon Sep 17 00:00:00 2001 From: Nuzair46 Date: Tue, 29 Jul 2025 14:50:05 +0900 Subject: [PATCH 2/3] Updated naming to tap_each --- enumerator.c | 18 +++++++++--------- test/ruby/test_lazy_enumerator.rb | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/enumerator.c b/enumerator.c index bc3ab25d16..302add746f 100644 --- a/enumerator.c +++ b/enumerator.c @@ -2776,7 +2776,7 @@ lazy_with_index(int argc, VALUE *argv, VALUE obj) } static struct MEMO * -lazy_peek_proc(VALUE proc_entry, struct MEMO *result, VALUE memos, long memo_index) +lazy_tap_each_proc(VALUE proc_entry, struct MEMO *result, VALUE memos, long memo_index) { struct proc_entry *entry = proc_entry_ptr(proc_entry); @@ -2785,13 +2785,13 @@ lazy_peek_proc(VALUE proc_entry, struct MEMO *result, VALUE memos, long memo_ind return result; } -static const lazyenum_funcs lazy_peek_funcs = { - lazy_peek_proc, 0, +static const lazyenum_funcs lazy_tap_each_funcs = { + lazy_tap_each_proc, 0, }; /* * call-seq: - * lazy.peek { |item| ... } -> lazy_enumerator + * lazy.tap_each { |item| ... } -> lazy_enumerator * * Passes each element through to the block for side effects only, * without modifying the element or affecting the enumeration. @@ -2801,7 +2801,7 @@ static const lazyenum_funcs lazy_peek_funcs = { * without breaking laziness or misusing +map+. * * (1..).lazy - * .peek { |x| puts "got #{x}" } + * .tap_each { |x| puts "got #{x}" } * .select(&:even?) * .first(3) * # prints: got 1, got 2, ..., got 6 @@ -2811,14 +2811,14 @@ static const lazyenum_funcs lazy_peek_funcs = { */ static VALUE -lazy_peek(VALUE obj) +lazy_tap_each(VALUE obj) { if (!rb_block_given_p()) { - rb_raise(rb_eArgError, "tried to call lazy peek without a block"); + rb_raise(rb_eArgError, "tried to call lazy tap_each without a block"); } - return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_peek_funcs); + return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_tap_each_funcs); } #if 0 /* for RDoc */ @@ -4670,7 +4670,7 @@ InitVM_Enumerator(void) rb_define_method(rb_cLazy, "uniq", lazy_uniq, 0); rb_define_method(rb_cLazy, "compact", lazy_compact, 0); rb_define_method(rb_cLazy, "with_index", lazy_with_index, -1); - rb_define_method(rb_cLazy, "peek", lazy_peek, 0); + rb_define_method(rb_cLazy, "tap_each", lazy_tap_each, 0); lazy_use_super_method = rb_hash_new_with_size(18); rb_hash_aset(lazy_use_super_method, sym("map"), sym("_enumerable_map")); diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb index 21ded2c60c..a46af4c26c 100644 --- a/test/ruby/test_lazy_enumerator.rb +++ b/test/ruby/test_lazy_enumerator.rb @@ -608,7 +608,7 @@ EOS end def test_require_block - %i[select reject drop_while take_while map flat_map peek].each do |method| + %i[select reject drop_while take_while map flat_map tap_each].each do |method| assert_raise(ArgumentError){ [].lazy.send(method) } end end @@ -716,11 +716,11 @@ EOS assert_equal(3, Enumerator::Lazy.new([1, 2, 3], 3){|y, v| y << v}.with_index.size) end - def test_lazy_peek + def test_lazy_tap_each out = [] e = (1..Float::INFINITY).lazy - .peek { |x| out << x } + .tap_each { |x| out << x } .select(&:even?) .first(5) @@ -728,10 +728,10 @@ EOS assert_equal([2, 4, 6, 8, 10], e) end - def test_lazy_peek_is_not_intrusive + def test_lazy_tap_each_is_not_intrusive s = Step.new(1..3) - assert_equal(2, s.lazy.peek { |x| x }.map { |x| x * 2 }.first) + assert_equal(2, s.lazy.tap_each { |x| x }.map { |x| x * 2 }.first) assert_equal(1, s.current) end end From 964165f198667607e783ea447cccc54c3c245d9b Mon Sep 17 00:00:00 2001 From: Nuzair46 Date: Wed, 30 Jul 2025 14:44:11 +0900 Subject: [PATCH 3/3] Updated naming to lazy_each as discussed --- enumerator.c | 18 +++++++++--------- test/ruby/test_lazy_enumerator.rb | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/enumerator.c b/enumerator.c index 302add746f..ad75a8a759 100644 --- a/enumerator.c +++ b/enumerator.c @@ -2776,7 +2776,7 @@ lazy_with_index(int argc, VALUE *argv, VALUE obj) } static struct MEMO * -lazy_tap_each_proc(VALUE proc_entry, struct MEMO *result, VALUE memos, long memo_index) +lazy_lazy_each_proc(VALUE proc_entry, struct MEMO *result, VALUE memos, long memo_index) { struct proc_entry *entry = proc_entry_ptr(proc_entry); @@ -2785,13 +2785,13 @@ lazy_tap_each_proc(VALUE proc_entry, struct MEMO *result, VALUE memos, long memo return result; } -static const lazyenum_funcs lazy_tap_each_funcs = { - lazy_tap_each_proc, 0, +static const lazyenum_funcs lazy_lazy_each_funcs = { + lazy_lazy_each_proc, 0, }; /* * call-seq: - * lazy.tap_each { |item| ... } -> lazy_enumerator + * lazy.lazy_each { |item| ... } -> lazy_enumerator * * Passes each element through to the block for side effects only, * without modifying the element or affecting the enumeration. @@ -2801,7 +2801,7 @@ static const lazyenum_funcs lazy_tap_each_funcs = { * without breaking laziness or misusing +map+. * * (1..).lazy - * .tap_each { |x| puts "got #{x}" } + * .lazy_each { |x| puts "got #{x}" } * .select(&:even?) * .first(3) * # prints: got 1, got 2, ..., got 6 @@ -2811,14 +2811,14 @@ static const lazyenum_funcs lazy_tap_each_funcs = { */ static VALUE -lazy_tap_each(VALUE obj) +lazy_lazy_each(VALUE obj) { if (!rb_block_given_p()) { - rb_raise(rb_eArgError, "tried to call lazy tap_each without a block"); + rb_raise(rb_eArgError, "tried to call lazy lazy_each without a block"); } - return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_tap_each_funcs); + return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_lazy_each_funcs); } #if 0 /* for RDoc */ @@ -4670,7 +4670,7 @@ InitVM_Enumerator(void) rb_define_method(rb_cLazy, "uniq", lazy_uniq, 0); rb_define_method(rb_cLazy, "compact", lazy_compact, 0); rb_define_method(rb_cLazy, "with_index", lazy_with_index, -1); - rb_define_method(rb_cLazy, "tap_each", lazy_tap_each, 0); + rb_define_method(rb_cLazy, "lazy_each", lazy_lazy_each, 0); lazy_use_super_method = rb_hash_new_with_size(18); rb_hash_aset(lazy_use_super_method, sym("map"), sym("_enumerable_map")); diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb index a46af4c26c..3e1046d2aa 100644 --- a/test/ruby/test_lazy_enumerator.rb +++ b/test/ruby/test_lazy_enumerator.rb @@ -608,7 +608,7 @@ EOS end def test_require_block - %i[select reject drop_while take_while map flat_map tap_each].each do |method| + %i[select reject drop_while take_while map flat_map lazy_each].each do |method| assert_raise(ArgumentError){ [].lazy.send(method) } end end @@ -716,11 +716,11 @@ EOS assert_equal(3, Enumerator::Lazy.new([1, 2, 3], 3){|y, v| y << v}.with_index.size) end - def test_lazy_tap_each + def test_lazy_lazy_each out = [] e = (1..Float::INFINITY).lazy - .tap_each { |x| out << x } + .lazy_each { |x| out << x } .select(&:even?) .first(5) @@ -728,10 +728,10 @@ EOS assert_equal([2, 4, 6, 8, 10], e) end - def test_lazy_tap_each_is_not_intrusive + def test_lazy_lazy_each_is_not_intrusive s = Step.new(1..3) - assert_equal(2, s.lazy.tap_each { |x| x }.map { |x| x * 2 }.first) + assert_equal(2, s.lazy.lazy_each { |x| x }.map { |x| x * 2 }.first) assert_equal(1, s.current) end end