[ruby-dev:31847] Re: generator from Enumerable#map

From: "Yusuke ENDOH" <mame@...>
Date: 2007-09-24 17:09:33 UTC
List: ruby-dev #31847
遠藤です。

> 1.9 では、Enumerable#map や Enumerable#index をブロックなしで呼ぶと
> generator を返すようですが、これは Object#to_enum と何か異なるの
> でしょうか。

情けないことにまだわかってません。気になるので教えていただけません
でしょうか。


> Enumerator で yield の返り値相当などを指定できるなら意味がありそうです。
> 例えば Enumerator#value= と #result を入れてこんな風に。
>
> g = [1, 2, 3].map
> loop { x = g.next; g.value = x * 2 }
> p g.result #=> [2, 4, 6]
>
>
> g = [:a, :b, :c].index
> loop { x = g.next; g.value = x == :b }
> p g.result #=> 1

この拡張が特別に欲しいわけではないのですが、外部イテレータを fiber の
代用とするためには無いと不便なので、試しに実装してみました。
# Fiber#resume の引数 (= Fiber.yield の返値) を指定できないので。

メソッド名やインターフェイスはもっといいのがありそうです。


Index: enumerator.c
===================================================================
--- enumerator.c	(revision 13509)
+++ enumerator.c	(working copy)
@@ -42,6 +42,8 @@
     VALUE method;
     VALUE proc;
     VALUE args;
+    VALUE value;
+    VALUE result;
     enum_iter *iter;
     VALUE fib;
     VALUE dst;
@@ -55,6 +57,8 @@
     rb_gc_mark(ptr->method);
     rb_gc_mark(ptr->proc);
     rb_gc_mark(ptr->args);
+    rb_gc_mark(ptr->value);
+    rb_gc_mark(ptr->result);
     rb_gc_mark(ptr->fib);
     rb_gc_mark(ptr->dst);
 }
@@ -238,6 +242,8 @@
 	ptr->iter = (enum_iter *)rb_yield;
     }
     if (argc) ptr->args = rb_ary_new4(argc, argv);
+    ptr->value = Qnil;
+    ptr->result = Qnil;
     ptr->fib = 0;
     ptr->dst = Qnil;
     ptr->no_next = Qfalse;
@@ -286,6 +292,8 @@
     ptr1->proc = ptr0->proc;
     ptr1->iter = ptr0->iter;
     ptr1->args = ptr0->args;
+    ptr1->value = ptr0->value;
+    ptr1->result = ptr0->result;
     ptr1->fib  = ptr0->fib;

     return obj;
@@ -371,8 +379,9 @@
 next_ii(VALUE i, VALUE obj)
 {
     struct enumerator *e = enumerator_ptr(obj);
+    e->value = Qnil;
     rb_fiber_yield(1, &i);
-    return Qnil;
+    return e->value;
 }

 static VALUE
@@ -381,7 +390,7 @@
     struct enumerator *e = enumerator_ptr(obj);
     VALUE nil = Qnil;

-    rb_block_call(obj, rb_intern("each"), 0, 0, next_ii, obj);
+    e->result = rb_block_call(obj, rb_intern("each"), 0, 0, next_ii, obj);
     e->no_next = Qtrue;
     return rb_fiber_yield(1, &nil);
 }
@@ -431,6 +440,36 @@

 /*
  * call-seq:
+ *   e.value = v   => v
+ *
+ * Sets the value that current block will return.
+ */
+
+static VALUE
+enumerator_set_value(VALUE obj, VALUE value) {
+    struct enumerator *e = enumerator_ptr(obj);
+
+    e->value = value;
+
+    return value;
+}
+
+/*
+ * call-seq:
+ *   e.result => object
+ *
+ * Returns the value that the iteration method returned.
+ */
+
+static VALUE
+enumerator_result(VALUE obj) {
+    struct enumerator *e = enumerator_ptr(obj);
+
+    return e->result;
+}
+
+/*
+ * call-seq:
  *   e.next?   => e
  *
  * Rewinds the enumeration sequence by the next method.
@@ -467,6 +506,8 @@
     rb_define_method(rb_cEnumerator, "to_splat", enumerator_to_splat, 0);
     rb_define_method(rb_cEnumerator, "next", enumerator_next, 0);
     rb_define_method(rb_cEnumerator, "rewind", enumerator_rewind, 0);
+    rb_define_method(rb_cEnumerator, "value=", enumerator_set_value, 1);
+    rb_define_method(rb_cEnumerator, "result", enumerator_result, 0);

     rb_eStopIteration   = rb_define_class("StopIteration", rb_eIndexError);


-- 
Yusuke ENDOH <mame@tsg.ne.jp>

In This Thread