Enumerable object from a block

 ブロックで簡単に Enumerable なオブジェクトを作れる機能を追åŠ
しようと思いますが、どうでしょうか。

 いちいちクラスを作ったり、 extend(Enumerable) して特異メソッド
each を定義したりしなくて済みます。

# フィボナッチ数列(n>=1)
fib = Enumerator.new { |y|
  a = b = 1
  loop {
    y << a
    a, b = b, a + b
  }
}

p fib.take(10) #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

 元は Ruby 1.8 の generator.rb についていたおまけ機能ですが、
手軽に無限リストや柔軟な繰り返しを定義できるので便利です。


Akinori MUSHA / http://akinori.org/

Index: enumerator.c

— enumerator.c (revision 18754)
+++ enumerator.c (working copy)
@@ -35,6 +35,22 @@ struct enumerator {
VALUE no_next;
};

+static VALUE rb_cGenerator, rb_cYielder;
+
+struct generator {

  • VALUE proc;
    +};

+struct yielder {

  • VALUE proc;
    +};

+static VALUE generator_allocate(VALUE klass);
+static VALUE generator_init(VALUE obj, VALUE proc);
+
+/*

    • Enumerator
  • */
    static void
    enumerator_mark(void p)
    {
    @@ -242,25 +258,53 @@ enumerator_init(VALUE enum_obj, VALUE ob
    /
    • call-seq:
    • Enumerator.new(obj, method = :each, *args)
    • Enumerator.new { |y| … }
    • Creates a new Enumerator object, which is to be used as an
    • Enumerable object using the given object’s given method with the
    • given arguments.
    • Enumerable object iterating in a given way.
    • Use of this method is discouraged. Use Kernel#enum_for() instead.
    • In the first form, a generated Enumerator iterates over the given
    • object using the given method with the given arguments passed.
    • Use of this form is discouraged. Use Kernel#enum_for(), alias
    • to_enum, instead.
    • e = Enumerator.new(ObjectSpace, :each_object)
    •    #-> ObjectSpace.enum_for(:each_object)
      
    • e.select { |obj| obj.is_a?(Class) } #=> array of all classes
    • In the second form, iteration is defined by the given block, in
    • which a “yielder” object given as block parameter can be used to
    • yield a value by calling the +yield+ method, alias +<<+.
    • fib = Enumerator.new { |y|
    •  a = b = 1
      
    •  loop {
      
    •    y << a
      
    •    a, b = b, a + b
      
    •  }
      
    • }
    • p fib.take(10) #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
      */
      static VALUE
      enumerator_initialize(int argc, VALUE *argv, VALUE obj)
      {
      VALUE recv, meth = sym_each;
  • if (argc == 0)
  • rb_raise(rb_eArgError, “wrong number of argument (0 for 1)”);
  • recv = *argv++;
  • if (–argc) {
  • meth = *argv++;
  • –argc;
  • if (argc == 0) {
  • if (!rb_block_given_p())
  •  rb_raise(rb_eArgError, "wrong number of argument (0 for 1+)");
    
  • recv = generator_init(generator_allocate(rb_cGenerator),
    rb_block_proc());
  • } else {
  • recv = *argv++;
  • if (–argc) {
  •  meth = *argv++;
    
  •  --argc;
    
  • }
    }
  • return enumerator_init(obj, recv, meth, argc, argv);
    }

@@ -471,6 +515,217 @@ enumerator_rewind(VALUE obj)
return obj;
}

+/*

    • Yielder
  • */
    +static void
    +yielder_mark(void *p)
    +{
  • struct yielder *ptr = p;
  • rb_gc_mark(ptr->proc);
    +}

+static struct yielder *
+yielder_ptr(VALUE obj)
+{

  • struct yielder *ptr;
  • Data_Get_Struct(obj, struct yielder, ptr);
  • if (RDATA(obj)->dmark != yielder_mark) {
  • rb_raise(rb_eTypeError,
  • "wrong argument type %s (expected %s)",
    
  • rb_obj_classname(obj), rb_class2name(rb_cYielder));
    
  • }
  • if (!ptr || ptr->proc == Qundef) {
  • rb_raise(rb_eArgError, “uninitialized yielder”);
  • }
  • return ptr;
    +}

+/* :nodoc: */
+static VALUE
+yielder_allocate(VALUE klass)
+{

  • struct yielder *ptr;
  • VALUE obj;
  • obj = Data_Make_Struct(klass, struct yielder, yielder_mark, -1,
    ptr);
  • ptr->proc = Qundef;
  • return obj;
    +}

+static VALUE
+yielder_init(VALUE obj, VALUE proc)
+{

  • struct yielder *ptr;
  • Data_Get_Struct(obj, struct yielder, ptr);
  • if (!ptr) {
  • rb_raise(rb_eArgError, “unallocated yielder”);
  • }
  • ptr->proc = proc;
  • return obj;
    +}

+/* :nodoc: */
+static VALUE
+yielder_initialize(VALUE obj)
+{

  • rb_need_block();
  • return yielder_init(obj, rb_block_proc());
    +}

+/* :nodoc: */
+static VALUE
+yielder_yield(VALUE obj, VALUE args)
+{

  • struct yielder *ptr = yielder_ptr(obj);
  • rb_proc_call(ptr->proc, args);
  • return obj;
    +}

+static VALUE
+yielder_new_i(VALUE dummy)
+{

  • return yielder_init(yielder_allocate(rb_cYielder),
    rb_block_proc());
    +}

+static VALUE
+yielder_yield_i(VALUE obj, VALUE memo, int argc, VALUE *argv)
+{

  • return rb_yield(obj);
    +}

+static VALUE
+yielder_new(void)
+{

  • return rb_iterate(yielder_new_i, (VALUE)0, yielder_yield_i,
    (VALUE)0);
    +}

+/*

    • Generator
  • */
    +static void
    +generator_mark(void *p)
    +{
  • struct generator *ptr = p;
  • rb_gc_mark(ptr->proc);
    +}

+static struct generator *
+generator_ptr(VALUE obj)
+{

  • struct generator *ptr;
  • Data_Get_Struct(obj, struct generator, ptr);
  • if (RDATA(obj)->dmark != generator_mark) {
  • rb_raise(rb_eTypeError,
  • "wrong argument type %s (expected %s)",
    
  • rb_obj_classname(obj), rb_class2name(rb_cGenerator));
    
  • }
  • if (!ptr || ptr->proc == Qundef) {
  • rb_raise(rb_eArgError, “uninitialized generator”);
  • }
  • return ptr;
    +}

+/* :nodoc: */
+static VALUE
+generator_allocate(VALUE klass)
+{

  • struct generator *ptr;
  • VALUE obj;
  • obj = Data_Make_Struct(klass, struct generator, generator_mark, -1,
    ptr);
  • ptr->proc = Qundef;
  • return obj;
    +}

+static VALUE
+generator_init(VALUE obj, VALUE proc)
+{

  • struct generator *ptr;
  • Data_Get_Struct(obj, struct generator, ptr);
  • if (!ptr) {
  • rb_raise(rb_eArgError, “unallocated generator”);
  • }
  • ptr->proc = proc;
  • return obj;
    +}

+VALUE rb_obj_is_proc(VALUE proc);
+
+/* :nodoc: */
+static VALUE
+generator_initialize(int argc, VALUE *argv, VALUE obj)
+{

  • VALUE proc;
  • if (argc == 0) {
  • rb_need_block();
  • proc = rb_block_proc();
  • } else {
  • rb_scan_args(argc, argv, “1”, &proc);
  • if (!rb_obj_is_proc(proc))
  •  rb_raise(rb_eTypeError,
    
  •     "wrong argument type %s (expected Proc)",
    
  •     rb_obj_classname(proc));
    
  • if (rb_block_given_p()) {
  •  rb_warn("given block not used");
    
  • }
  • }
  • return generator_init(obj, proc);
    +}

+/* :nodoc: */
+static VALUE
+generator_init_copy(VALUE obj, VALUE orig)
+{

  • struct generator *ptr0, *ptr1;
  • ptr0 = generator_ptr(orig);
  • Data_Get_Struct(obj, struct generator, ptr1);
  • if (!ptr1) {
  • rb_raise(rb_eArgError, “unallocated generator”);
  • }
  • ptr1->proc = ptr0->proc;
  • return obj;
    +}

+/* :nodoc: */
+static VALUE
+generator_each(VALUE obj)
+{

  • struct generator *ptr = generator_ptr(obj);
  • VALUE yielder;
  • yielder = yielder_new();
  • rb_proc_call(ptr->proc, rb_ary_new3(1, yielder));
  • return obj;
    +}

void
Init_Enumerator(void)
{
@@ -497,9 +752,24 @@ Init_Enumerator(void)
rb_define_method(rb_cEnumerator, “next”, enumerator_next, 0);
rb_define_method(rb_cEnumerator, “rewind”, enumerator_rewind, 0);

  • rb_eStopIteration = rb_define_class(“StopIteration”,
    rb_eIndexError);
  • rb_eStopIteration = rb_define_class(“StopIteration”,
    rb_eIndexError);
  • sym_each = ID2SYM(rb_intern(“each”));
  • /* Generator */

  • rb_cGenerator = rb_define_class_under(rb_cEnumerator, “Generator”,
    rb_cObject);

  • rb_include_module(rb_cGenerator, rb_mEnumerable);

  • rb_define_alloc_func(rb_cGenerator, generator_allocate);

  • rb_define_method(rb_cGenerator, “initialize”, generator_initialize,
    -1);

  • rb_define_method(rb_cGenerator, “initialize_copy”,
    generator_init_copy, 1);

  • rb_define_method(rb_cGenerator, “each”, generator_each, 0);

  • /* Yielder */

  • rb_cYielder = rb_define_class_under(rb_cEnumerator, “Yielder”,
    rb_cObject);

  • rb_define_alloc_func(rb_cYielder, yielder_allocate);

  • rb_define_method(rb_cYielder, “initialize”, yielder_initialize, 0);

  • rb_define_method(rb_cYielder, “yield”, yielder_yield, -2);

  • rb_define_method(rb_cYielder, “<<”, yielder_yield, -2);

  • sym_each = ID2SYM(rb_intern(“each”));

    rb_provide(“enumerator.so”); /* for backward compatibility */
    }

e$B$^$D$b$He(B e$B$f$-$R$m$G$9e(B

In message “Re: [ruby-dev:35903] Enumerable object from a block”
on Thu, 21 Aug 2008 22:19:15 +0900, “Akinori MUSHA”
[email protected] writes:

|e$B!!%V%m%C%/$G4JC1$Ke(B Enumerable e$B$J%%V%8%'%/%H$r:n$l$k5!G=$rDI2Ce(B
|e$B$7$h$&$H;W$$$^$9$,!"$I$&$G$7$g$&$+!#e(B
|
|e$B!!$$$A$$$A%/%i%9$r:n$C$?$j!"e(B extend(Enumerable) e$B$7$FFC0[%a%=%C%Ie(B
|each e$B$rDj5A$7$?$j$7$J$/$F:Q$_$^$9!#e(B
|
| # e$B%U%#%%J%C%A?tNse(B(n>=1)
| fib = Enumerator.new { |y|
| a = b = 1
| loop {
| y << a
| a, b = b, a + b
| }
| }
|
| p fib.take(10) #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
|
|
|e$B!!85$Oe(B Ruby 1.8 e$B$Ne(B generator.rb e$B$K$D$$$F$$$?$
$^$15!G=$G$9$,!"e(B
|e$B<j7Z$KL58B%j%9%H$d=@Fp$J7+$jJV$7$rDj5A$G$-$k$N$GJXMx$G$9!#e(B

e$B$^$“!”$$$$$s$8$c$J$$$G$7$g$&$+e(B(1.9e$B$J$ie(B)e$B!#$“$H!”%V%m%C%/0z?te(B
e$B$+$i$NN`?d$G$$$($P!"e(Byieldere$B$Oe(Bcalle$B%a%=%C%I$r;}$C$F$$$?$[$&$,e(B
e$B$h$$$+$b$7$l$^$;$s!#e(B

At Fri, 22 Aug 2008 07:54:53 +0900,
matz wrote:

| fib = Enumerator.new { |y|
| 元は Ruby 1.8 の generator.rb についていたおまけ機能ですが、
|手軽に無限リストや柔軟な繰り返しを定義できるので便利です。

まあ、いいんじゃないでしょうか(1.9なら)。あと、ブロック引数

 trunkに実験的に入れてみました。

からの類推でいえば、yielderはcallメソッドを持っていたほうが
よいかもしれません。

 << と同等の yield メソッドもあります。しかし、
yielder.yield(value) の重複感が気持ち悪いことは否めません。

 きっと Yielder という名前が安直で良くないんでしょうね。