ruby-changes:27005
From: marcandre <ko1@a...>
Date: Tue, 5 Feb 2013 12:49:52 +0900 (JST)
Subject: [ruby-changes:27005] marcandRe: r39057 (trunk): * enumerator.c: Finalize and document Lazy.new. [Bug #7248]
marcandre 2013-02-05 12:49:41 +0900 (Tue, 05 Feb 2013) New Revision: 39057 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=39057 Log: * enumerator.c: Finalize and document Lazy.new. [Bug #7248] Add Lazy#to_enum and simplify Lazy#size. * test/ruby/test_lazy_enumerator.rb: tests for above Modified files: trunk/ChangeLog trunk/enumerator.c trunk/test/ruby/test_lazy_enumerator.rb Index: ChangeLog =================================================================== --- ChangeLog (revision 39056) +++ ChangeLog (revision 39057) @@ -1,3 +1,10 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1 +Tue Feb 5 12:48:10 2013 Marc-Andre Lafortune <ruby-core@m...> + + * enumerator.c: Finalize and document Lazy.new. [Bug #7248] + Add Lazy#to_enum and simplify Lazy#size. + + * test/ruby/test_lazy_enumerator.rb: tests for above + Tue Feb 5 11:35:35 2013 Eric Hodel <drbrain@s...> * lib/rubygems/commands/push_command.rb: Fixed credential download for Index: enumerator.c =================================================================== --- enumerator.c (revision 39056) +++ enumerator.c (revision 39057) @@ -457,6 +457,9 @@ rb_enumeratorize(VALUE obj, VALUE meth, https://github.com/ruby/ruby/blob/trunk/enumerator.c#L457 return rb_enumeratorize_with_size(obj, meth, argc, argv, 0); } +static VALUE +lazy_to_enum_i(VALUE self, VALUE meth, int argc, VALUE *argv, VALUE (*size_fn)(ANYARGS)); + VALUE rb_enumeratorize_with_size(VALUE obj, VALUE meth, int argc, VALUE *argv, VALUE (*size_fn)(ANYARGS)) { @@ -1022,7 +1025,7 @@ enumerator_size(VALUE obj) https://github.com/ruby/ruby/blob/trunk/enumerator.c#L1025 struct enumerator *e = enumerator_ptr(obj); if (e->size_fn) { - return (*e->size_fn)(e->obj, e->args); + return (*e->size_fn)(e->obj, e->args, obj); } if (rb_obj_is_proc(e->size)) { if (e->args) @@ -1271,20 +1274,22 @@ generator_each(int argc, VALUE *argv, VA https://github.com/ruby/ruby/blob/trunk/enumerator.c#L1274 /* Lazy Enumerator methods */ static VALUE -lazy_receiver_size(VALUE self) +enum_size(VALUE self) { - VALUE r = rb_check_funcall(rb_ivar_get(self, id_receiver), id_size, 0, 0); + VALUE r = rb_check_funcall(self, id_size, 0, 0); return (r == Qundef) ? Qnil : r; } static VALUE lazy_size(VALUE self) { - struct enumerator *e = enumerator_ptr(self); - if (e->size_fn) { - return (*e->size_fn)(self); - } - return Qnil; + return enum_size(rb_ivar_get(self, id_receiver)); +} + +static VALUE +lazy_receiver_size(VALUE generator, VALUE args, VALUE lazy) +{ + return lazy_size(lazy); } static VALUE @@ -1314,54 +1319,57 @@ lazy_init_iterator(VALUE val, VALUE m, i https://github.com/ruby/ruby/blob/trunk/enumerator.c#L1319 } static VALUE -lazy_init_yielder(VALUE val, VALUE m, int argc, VALUE *argv) -{ - VALUE result; - result = rb_funcall2(m, id_yield, argc, argv); - if (result == Qundef) rb_iter_break(); - return Qnil; -} - -static VALUE lazy_init_block_i(VALUE val, VALUE m, int argc, VALUE *argv) { rb_block_call(m, id_each, argc-1, argv+1, lazy_init_iterator, val); return Qnil; } -static VALUE -lazy_init_block(VALUE val, VALUE m, int argc, VALUE *argv) -{ - rb_block_call(m, id_each, argc-1, argv+1, lazy_init_yielder, val); - return Qnil; -} - +/* + * call-seq: + * Lazy.new(obj, size=nil) { |yielder, *values| ... } + * + * Creates a new Lazy enumerator. When the enumerator is actually enumerated + * (e.g. by calling #force), +obj+ will be enumerated and each value passed + * to the given block. The block can yield values back using +yielder+. + * For example, to create a method +filter_map+ in both lazy and + * non-lazy fashions: + * + * module Enumerable + * def filter_map(&block) + * map(&block).compact + * end + * end + * + * class Enumerator::Lazy + * def filter_map + * Lazy.new(self) do |yielder, *values| + * result = yield *values + * yielder << result if result + * end + * end + * end + * + * (1..Float::INFINITY).lazy.filter_map{|i| i*i if i.even?}.first(5) + * # => [4, 16, 36, 64, 100] + */ static VALUE lazy_initialize(int argc, VALUE *argv, VALUE self) { - VALUE obj, meth; + VALUE obj, size = Qnil; VALUE generator; - int offset; - if (argc < 1) { - rb_raise(rb_eArgError, "wrong number of arguments (%d for 1..)", argc); + rb_check_arity(argc, 1, 2); + if (!rb_block_given_p()) { + rb_raise(rb_eArgError, "tried to call lazy new without a block"); } - else { - obj = argv[0]; - if (argc == 1) { - meth = sym_each; - offset = 1; - } - else { - meth = argv[1]; - offset = 2; - } + obj = argv[0]; + if (argc > 1) { + size = argv[1]; } generator = generator_allocate(rb_cGenerator); - rb_block_call(generator, id_initialize, 0, 0, - (rb_block_given_p() ? lazy_init_block_i : lazy_init_block), - obj); - enumerator_init(self, generator, meth, argc - offset, argv + offset, lazy_receiver_size, Qnil); + rb_block_call(generator, id_initialize, 0, 0, lazy_init_block_i, obj); + enumerator_init(self, generator, sym_each, 0, 0, 0, size); rb_ivar_set(self, id_receiver, obj); return self; @@ -1418,15 +1426,59 @@ lazy_set_method(VALUE lazy, VALUE args, https://github.com/ruby/ruby/blob/trunk/enumerator.c#L1426 static VALUE enumerable_lazy(VALUE obj) { - VALUE result; - - result = rb_class_new_instance(1, &obj, rb_cLazy); + VALUE result = lazy_to_enum_i(obj, sym_each, 0, 0, enum_size); /* Qfalse indicates that the Enumerator::Lazy has no method name */ rb_ivar_set(result, id_method, Qfalse); return result; } static VALUE +lazy_to_enum_i(VALUE obj, VALUE meth, int argc, VALUE *argv, VALUE (*size_fn)(ANYARGS)) +{ + return enumerator_init(enumerator_allocate(rb_cLazy), + obj, meth, argc, argv, size_fn, Qnil); +} + +/* + * call-seq: + * lzy.to_enum(method = :each, *args) -> lazy_enum + * lzy.enum_for(method = :each, *args) -> lazy_enum + * lzy.to_enum(method = :each, *args) {|*args| block} -> lazy_enum + * lzy.enum_for(method = :each, *args){|*args| block} -> lazy_enum + * + * Similar to Kernel#to_enum, except it returns a lazy enumerator. + * This makes it easy to define Enumerable methods that will + * naturally remain lazy if called from a lazy enumerator. + * + * For example, continuing from the example in Kernel#to_enum: + * + * # See Kernel#to_enum for the definition of repeat + * r = 1..Float::INFINITY + * r.repeat(2).first(5) # => [1, 1, 2, 2, 3] + * r.repeat(2).class # => Enumerator + * r.repeat(2).map{|n| n ** 2}.first(5) # => endless loop! + * # works naturally on lazy enumerator: + * r.lazy.repeat(2).class # => Enumerator::Lazy + * r.lazy.repeat(2).map{|n| n ** 2}.first(5) # => [1, 1, 4, 4, 9] + */ + +static VALUE +lazy_to_enum(int argc, VALUE *argv, VALUE self) +{ + VALUE lazy, meth = sym_each; + + if (argc > 0) { + --argc; + meth = *argv++; + } + lazy = lazy_to_enum_i(self, meth, argc, argv, 0); + if (rb_block_given_p()) { + enumerator_ptr(lazy)->size = rb_block_proc(); + } + return lazy; +} + +static VALUE lazy_map_func(VALUE val, VALUE m, int argc, VALUE *argv) { VALUE result = rb_yield_values2(argc - 1, &argv[1]); @@ -1723,10 +1775,10 @@ lazy_take_func(VALUE val, VALUE args, in https://github.com/ruby/ruby/blob/trunk/enumerator.c#L1775 } static VALUE -lazy_take_size(VALUE lazy) +lazy_take_size(VALUE generator, VALUE args, VALUE lazy) { + VALUE receiver = lazy_size(lazy); long len = NUM2LONG(RARRAY_PTR(rb_ivar_get(lazy, id_arguments))[0]); - VALUE receiver = lazy_receiver_size(lazy); if (NIL_P(receiver) || (FIXNUM_P(receiver) && FIX2LONG(receiver) < len)) return receiver; return LONG2NUM(len); @@ -1736,21 +1788,20 @@ static VALUE https://github.com/ruby/ruby/blob/trunk/enumerator.c#L1788 lazy_take(VALUE obj, VALUE n) { long len = NUM2LONG(n); - int argc = 1; - VALUE argv[3]; + VALUE lazy; if (len < 0) { rb_raise(rb_eArgError, "attempt to take negative size"); } - argv[0] = obj; if (len == 0) { - argv[1] = sym_cycle; - argv[2] = INT2NUM(0); - argc = 3; - } - return lazy_set_method(rb_block_call(rb_cLazy, id_new, argc, argv, - lazy_take_func, n), - rb_ary_new3(1, n), lazy_take_size); + VALUE len = INT2NUM(0); + lazy = lazy_to_enum_i(obj, sym_cycle, 1, &len, 0); + } + else { + lazy = rb_block_call(rb_cLazy, id_new, 1, &obj, + lazy_take_func, n); + } + return lazy_set_method(lazy, rb_ary_new3(1, n), lazy_take_size); } static VALUE @@ -1774,10 +1825,10 @@ lazy_take_while(VALUE obj) https://github.com/ruby/ruby/blob/trunk/enumerator.c#L1825 } static VALUE -lazy_drop_size(VALUE lazy) +lazy_drop_size(VALUE generator, VALUE args, VALUE lazy) { long len = NUM2LONG(RARRAY_PTR(rb_ivar_get(lazy, id_arguments))[0]); - VALUE receiver = lazy_receiver_size(lazy); + VALUE receiver = lazy_size(lazy); if (NIL_P(receiver)) return receiver; if (FIXNUM_P(receiver)) { @@ -1842,36 +1893,12 @@ lazy_drop_while(VALUE obj) https://github.com/ruby/ruby/blob/trunk/enumerator.c#L1893 } static VALUE -lazy_cycle_size(VALUE lazy) -{ - return rb_enum_cycle_size(rb_ivar_get(lazy, id_receiver), rb_ivar_get(lazy, id_arguments)); -} - -static VALUE -lazy_cycle_func(VALUE val, VALUE m, int argc, VALUE *argv) -{ - return rb_funcall2(argv[0], id_yield, argc - 1, argv + 1); -} - -static VALUE lazy_cycle(int argc, VALUE *argv, VALUE obj) { - VALUE args; - int len = rb_long2int((long)argc + 2); - if (rb_block_given_p()) { return rb_call_super(argc, argv); } - args = rb_ary_tmp_new(len); - rb_ary_push(args, obj); - rb_ary_push(args, sym_cycle); - if (argc > 0) { - rb_ary_cat(args, argv, argc); - } - return lazy_set_method(rb_block_call(rb_cLazy, id_new, len, - RARRAY_PTR(args), lazy_cycle_func, - args /* prevent from GC */), - rb_ary_new4(argc, argv), lazy_cycle_size); + return lazy_to_enum_i(obj, sym_cycle, argc, argv, rb_enum_cycle_size); } static VALUE @@ -1963,12 +1990,13 @@ InitVM_Enumerator(void) https://github.com/ruby/ruby/blob/trunk/enumerator.c#L1990 rb_cLazy = rb_define_class_under(rb_cEnumerator, "Lazy", rb_cEnumerator); rb_define_method(rb_mEnumerable, "lazy", enumerable_lazy, 0); rb_define_method(rb_cLazy, "initialize", lazy_initialize, -1); + rb_define_method(rb_cLazy, "to_enum", lazy_to_enum, -1); + rb_define_method(rb_cLazy, "enum_for", lazy_to_enum, -1); rb_define_method(rb_cLazy, "map", lazy_map, 0); rb_define_method(rb_cLazy, "collect", lazy_map, 0); rb_define_method(rb_cLazy, "flat_map", lazy_flat_map, 0); rb_define_method(rb_cLazy, "collect_concat", lazy_flat_map, 0); rb_define_method(rb_cLazy, "select", lazy_select, 0); - rb_define_method(rb_cLazy, "size", lazy_size, 0); rb_define_method(rb_cLazy, "find_all", lazy_select, 0); rb_define_method(rb_cLazy, "reject", lazy_reject, 0); rb_define_method(rb_cLazy, "grep", lazy_grep, 1); Index: test/ruby/test_lazy_enumerator.rb =================================================================== --- test/ruby/test_lazy_enumerator.rb (revision 39056) +++ test/ruby/test_lazy_enumerator.rb (revision 39057) @@ -20,7 +20,8 @@ class TestLazyEnumerator < Test::Unit::T https://github.com/ruby/ruby/blob/trunk/test/ruby/test_lazy_enumerator.rb#L20 def test_initialize assert_equal([1, 2, 3], [1, 2, 3].lazy.to_a) - assert_equal([1, 2, 3], Enumerator::Lazy.new([1, 2, 3]).to_a) + assert_equal([1, 2, 3], Enumerator::Lazy.new([1, 2, 3]){|y, v| y << v}.to_a) + assert_raise(ArgumentError) { Enumerator::Lazy.new([1, 2, 3]) } end def test_each_args @@ -361,10 +362,6 @@ class TestLazyEnumerator < Test::Unit::T https://github.com/ruby/ruby/blob/trunk/test/ruby/test_lazy_enumerator.rb#L362 end def test_inspect - assert_equal("#<Enumerator::Lazy: 1..10:each>", - Enumerator::Lazy.new(1..10).inspect) - assert_equal("#<Enumerator::Lazy: 1..10:cycle(2)>", - Enumerator::Lazy.new(1..10, :cycle, 2).inspect) assert_equal("#<Enumerator::Lazy: 1..10>", (1..10).lazy.inspect) assert_equal('#<Enumerator::Lazy: #<Enumerator: "foo":each_char>>', "foo".each_char.lazy.inspect) @@ -388,10 +385,28 @@ class TestLazyEnumerator < Test::Unit::T https://github.com/ruby/ruby/blob/trunk/test/ruby/test_lazy_enumerator.rb#L385 EOS end + def test_lazy_to_enum + lazy = [1, 2, 3].lazy + def lazy.foo(*args) + yield args + yield args + end + enum = lazy.to_enum(:foo, :hello, :world) + assert_equal Enumerator::Lazy, enum.class + assert_equal nil, enum.size + assert_equal [[:hello, :world], [:hello, :world]], enum.to_a + + assert_equal [1, 2, 3], lazy.to_enum.to_a + end + def test_size lazy = [1, 2, 3].lazy assert_equal 3, lazy.size - assert_equal 42, Enumerator.new(42){}.lazy.size + assert_equal 42, Enumerator::Lazy.new([],->{42}){}.size + assert_equal 42, Enumerator::Lazy.new([],42){}.size + assert_equal 42, Enumerator::Lazy.new([],42){}.lazy.size + assert_equal 42, lazy.to_enum{ 42 }.size + %i[map collect].each do |m| assert_equal 3, lazy.send(m){}.size end -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/