[Feature:1.9] Enumerator#inspect

e$B1sF#$G$9!#e(B

Enumerator e$B$NCf?H$r$o$+$j$d$9$/I=<($7$F$/$l$ke(B Enumerator#inspect
e$B$re(B
e$BDs6!$9$k$HJXMx$@$H;W$$$^$9!#Nc$($P$3$s$J46$8!#e(B

$ ruby19 -e ‘g = (1…1000).each; p g’
#<Enumerator:[1, 2, 3, …(snipped)]>

e$B8=>u$Oe(B #Enumerator:0x8269654
e$B$H$+$J$N$G!"%G%P%C%0$K$OITJX$G$9!#e(B

e$B0J2<$O$?$?$-Bf$N%Q%C%A$G$9!#e(B

Index: enumerator.c

— enumerator.c (revision 20448)
+++ enumerator.c (working copy)
@@ -545,7 +545,69 @@
return obj;
}

+static VALUE
+inspect_enumerator_i(VALUE val, VALUE *memo)
+{

  • VALUE str = memo[0];
  • if (memo[1] >= 3) {
  • rb_str_buf_cat2(str, “, …(snipped)”);
  • rb_iter_break();
  • }
  • if (memo[1] > 0) rb_str_buf_cat2(str, ", ");
  • rb_str_buf_append(str, rb_inspect(val));
  • memo[1]++;
  • if (OBJ_TAINTED(val)) memo[2] = Qtrue;
  • if (OBJ_UNTRUSTED(val)) memo[3] = Qtrue;
  • return Qnil;
    +}

+static VALUE
+inspect_enumerator(VALUE obj, VALUE dummy, int recur)
+{

  • const char *cname = rb_obj_classname(obj);
  • struct enumerator *e;
  • int argc = 0;
  • VALUE *argv = 0;
  • VALUE str;
  • VALUE memo[4];
  • if (recur) {
  • str = rb_sprintf("#<%s:[…]>", cname);
  • OBJ_TAINT(str);
  • return str;
  • }
  • memo[0] = str = rb_sprintf("#<%s:[", cname);
  • memo[1] = (VALUE)0;
  • memo[2] = OBJ_TAINTED(obj);
  • memo[3] = OBJ_UNTRUSTED(obj);
  • e = enumerator_ptr(obj);
  • if (e->args) {
  • argc = RARRAY_LEN(e->args);
  • argv = RARRAY_PTR(e->args);
  • }
  • rb_block_call(e->obj, e->meth, argc, argv,
  •  inspect_enumerator_i, (VALUE)memo);
    
  • rb_str_buf_cat2(str, “]>”);
  • if (memo[2]) OBJ_TAINT(str);
  • if (memo[3]) OBJ_UNTRUST(str);
  • return str;
    +}

/*

    • call-seq:
    • e.inspect => “#<Enumerator:[1, 2, 3, …(snipped)]>”
    • Create a printable version of e.
  • */

+static VALUE
+enumerator_inspect(VALUE obj)
+{

  • return rb_exec_recursive(inspect_enumerator, obj, 0);
    +}

+/*

  • Yielder
    */
    static void
    @@ -779,6 +841,7 @@
    rb_define_method(rb_cEnumerator, “with_object”,
    enumerator_with_object, 1);
    rb_define_method(rb_cEnumerator, “next”, enumerator_next, 0);
    rb_define_method(rb_cEnumerator, “rewind”, enumerator_rewind, 0);
  • rb_define_method(rb_cEnumerator, “inspect”, enumerator_inspect, 0);

    rb_eStopIteration = rb_define_class(“StopIteration”,
    rb_eIndexError);

Index: lib/pp.rb

— lib/pp.rb (revision 20448)
+++ lib/pp.rb (working copy)
@@ -339,6 +339,27 @@
end
end

+class Enumerator

  • def pretty_print(q)
  • s = self.class.to_s
  • q.group(s.size + 3, ‘#<’ + s + ‘:’, ‘]>’) {
  •  idx = 0
    
  •  q.seplist(take(4)) {|v|
    
  •    if idx < 3
    
  •      q.pp v
    
  •    else
    
  •      q.text "...(snipped)"
    
  •    end
    
  •    idx += 1
    
  •  }
    
  • }
  • end
  • def pretty_print_cycle(q)
  • q.text(’#<’ + self.class.to_s + ‘:[…]’)
  • end
    +end

class Hash
def pretty_print(q)
q.pp_hash self

At Tue, 2 Dec 2008 23:59:52 +0900,
Yusuke ENDOH wrote:

Enumerator の中身をわかりやすく表示してくれる Enumerator#inspect を
提供すると便利だと思います。例えばこんな感じ。

$ ruby19 -e ‘g = (1…1000).each; p g’
#<Enumerator:[1, 2, 3, …(snipped)]>

現状は #Enumerator:0x8269654 とかなので、デバッグには不便です。

 inspect で実際に回してしまうのは(中身を見るための)副作用として
大きすぎ、適切ではない気がします。

 Enumerable ã ã¨åˆ†ã‹ã£ã¦ã„ã‚‹ã‚ªãƒ–ã‚¸ã‚§ã‚¯ãƒˆã®é ­ã®æ–¹ãŒè¦‹ãŸã„ã¨ã„ã†
だけなら

ruby19 -e ‘g = (1…1000).each; p g.take(3)’

くらいでいいと思いますが、想定するのはどういう使い道でしょうか。

 なお、 Enumerator が内包するオブジェクトへのアクセス方法を提供
していないのは意図的です。

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

In message “Re: [ruby-dev:37259] Re: [Feature:1.9] Enumerator#inspect”
on Wed, 3 Dec 2008 17:32:59 +0900, “Akinori MUSHA”
[email protected] writes:

|> Enumerator e$B$NCf?H$r$o$+$j$d$9$/I=<($7$F$/$l$ke(B Enumerator#inspect e$B$re(B
|> e$BDs6!$9$k$HJXMx$@$H;W$$$^$9!#Nc$($P$3$s$J46$8!#e(B
|>
|> $ ruby19 -e ‘g = (1…1000).each; p g’
|> #<Enumerator:[1, 2, 3, …(snipped)]>
|>
|> e$B8=>u$Oe(B #Enumerator:0x8269654 e$B$H$+$J$N$G!"%G%P%C%0$K$OITJX$G$9!#e(B
|
|e$B!!e(Binspect e$B$G<B:]$K2s$7$F$7$^$&$N$Oe(B(e$BCf?H$r8+$k$?$a$Ne(B)e$BI{:nMQ$H$7$Fe(B
|e$BBg$-$9$.!"E,@Z$G$O$J$$5$$,$7$^$9!#e(B

e$B$=$&$G$9$M$(!#e(BIOe$B$N$h$&$K:F3+2DG=$G$J$$e(BEnumerablee$B$b$“$j$^$9$7e(B
e$B$M$(!#;d$b$”$^$j;?@.$G$-$^$;$s!#$o$+$j$d$9$/$9$k$H$$$&%“%$%G%#e(B
e$B%”$K$OH?BP$G$O$J$$$s$G$9$,!#e(B

Enumeratore$B$NCf$K:G=i$Ne(B3e$BMWAG$r<h$C$F$*$/$H$+$O!"$?$V$s$d$j$9$.e(B
e$B$G$7$g$&$M$(!#e(B

e$B$3$s$K$A$O!"$J$+$`$ie(B(e$B$&e(B)e$B$G$9!#e(B

In message “[ruby-dev:37261] Re: [Feature:1.9] Enumerator#inspect”
on Dec.03,2008 18:17:53, [email protected] wrote:

e$B$=$&$G$9$M$(!#e(BIOe$B$N$h$&$K:F3+2DG=$G$J$$e(BEnumerablee$B$b$“$j$^$9$7e(B
e$B$M$(!#;d$b$”$^$j;?@.$G$-$^$;$s!#$o$+$j$d$9$/$9$k$H$$$&%“%$%G%#e(B
e$B%”$K$OH?BP$G$O$J$$$s$G$9$,!#e(B

Enumeratore$B$NCf$K:G=i$Ne(B3e$BMWAG$r<h$C$F$*$/$H$+$O!"$?$V$s$d$j$9$.e(B
e$B$G$7$g$&$M$(!#e(B

e$B<B8=2DG=$+$I$&$+$O9MN8$7$F$J$$$s$G$9$,!"<B:]$NCf?H$h$j$b$=$Ne(B
Enumeratore$B%*%V%8%'%/%H$,$I$3$+$i=P$F$-$?$N$+$,$o$+$kJ}$,4r$7e(B
e$B$$$h$&$J5$$,$7$^$9!#e(B
e$B>e5-$NNc$@$H!"Nc$($Pe(B
#Enumerator:Range#each
e$B$H$+!#e(B

e$B$G$b!“$I$s$J=PJ}$,4r$7$$$+$O!”%G%P%C%0$N$d$jJ}$K$h$k$s$G$7$ge(B
e$B$&$M$(!#e(B
e$B$J$*!“;d$OF0$$$F$k%9%/%j%W%H$NCf$Ke(Bpe$B$r2?8D$+$P$i$^$$$F5sF0$rDIe(B
e$B$C$?$j$9$k$N$G!“e(Bpe$B$NM-L5$Ge(B(stdoute$B$X$N=PNO0J30$Ke(B)e$BF0:n$,JQ2=$7$Fe(B
e$B$7$^$&2DG=@-$,$”$k$N$O$”$s$^$j4r$7$/$J$$$G$9!#e(B

e$B$=$l$G$O!#e(B

e$B1sF#$G$9!#e(B

2008/12/03 19:57 Akinori MUSHA [email protected]:

|> e$B8=>u$Oe(B #Enumerator:0x8269654 e$B$H$+$J$N$G!"%G%P%C%0$K$OITJX$G$9!#e(B

e$B$H$$$&46$8$G$I$&$G$7$g$&$+!#e(B(e$B2<$KE:IUe(B)

e$B$$!“MW$O!Ve(BEnumerator e$B$N@5BN$re(B p e$B$@$1$Ge(B
(e$B$”$kDxEYe(B) e$BCN$j$?$$!W$H$$$&e(B
e$B$3$H$J$N$G!“$3$l$G==J,$G$9!#e(B
. e$B$G$O$J$/e(B :
e$B$J$N$,$A$g$C$H0z$C$+$+$j$^$9$,!”:Y$+$$$3$H$G$9!#e(B

e$B40A4$KK~B-$7$?$N$G!"0J2<$O<XB-$G$9$,!#e(B

e$BI{:nMQ$N$“$k%a%=%C%I$+$ie(B Enumerator
e$B$r:n$C$FJQ$J$3$H$K$J$k$N$O!”<+8J@UG$e(B
e$B$@$H;W$C$F$$$^$7$?!#e(B
e$B$J$<$+$H$$$&$H!"e(BIO#each e$B$+$i:n$C$?e(B Enumerator e$B$re(B
rewind e$B$7$F$bL5;k$5$l$ke(B
e$B$H$$$&e(B (e$B;d$K$H$C$F$Oe(B) e$BIT2D2r$J5sF0$r$9$k$?$a$G$9!#e(B

$ ruby19 -rstringio -e ’
s = StringIO.new(“foo\nbar\n”).each
p s.next
s.rewind
p s.next

“foo\n”
“bar\n”

e$B$3$l$+$i2?$r46$8<h$C$?$+$H$$$&$He(B

  • rewind e$B$O<B:]$K$Oe(B rewind
    e$B$;$:!“<o$N%a%=%C%I$r8F$SD>$9$H$$$&0UL#$G$”$ke(B
  • e$B$=$l$K$b4X$o$i$:e(B rewind e$B$H$$$&L>A0$,IU$1$i$l$F$$$ke(B
  • Enumerator
    e$B$N<o$H$J$k%a%=%C%I$O8F$SD>$9$H:G=i$+$iNs5s$7;O$a$k$H$$$&e(B
    e$B@-<Ae(B (e$B$D$^$je(B Array#each e$B$N$h$&$J$Ne(B)
    e$B$,4|BT$5$l$F$$$ke(B
  • e$B$=$l<+BN$,I{:nMQ$r;}$D$h$&$J%a%=%C%I$Ge(B Enumerator
    e$B$r:n$k$N$O<+8J@UG$e(B

e$B$H$$$&J70O5$$G$7$?!#<XB-$G$7$?!#e(B

At Wed, 3 Dec 2008 18:39:59 +0900,
U.Nakamura wrote:

| inspect で実際に回してしまうのは(中身を見るための)副作用として
Enumeratorオブジェクトがどこから出てきたのかがわかる方が嬉し
いような気がします。
上記の例だと、例えば
#Enumerator:Range#each
とか。

まあ、デバッグ用だから見せてしまってもいいかもしれませんね。

(1…100).each_cons(2) => “#<Enumerator: 1…100:each_cons(2)>”

という感じでどうでしょうか。(下に添付)

でも、どんな出方が嬉しいかは、デバッグのやり方によるんでしょ
うねえ。
なお、私は動いてるスクリプトの中にpを何個かばらまいて挙動を追
ったりするので、pの有無で(stdoutへの出力以外に)動作が変化して
しまう可能性があるのはあんまり嬉しくないです。

私もそう思います。

Index: enumerator.c

— enumerator.c (revision 20453)
+++ enumerator.c (working copy)
@@ -545,6 +545,68 @@ enumerator_rewind(VALUE obj)
return obj;
}

+static VALUE
+inspect_enumerator(VALUE obj, VALUE dummy, int recur)
+{

  • struct enumerator *e = enumerator_ptr(obj);
  • const char *cname = rb_obj_classname(obj);
  • VALUE eobj, str;
  • int tainted, untrusted;
  • if (recur) {
  • str = rb_sprintf("#<%s: …>", cname);
  • OBJ_TAINT(str);
  • return str;
  • }
  • eobj = e->obj;
  • tainted = OBJ_TAINTED(eobj);
  • untrusted = OBJ_UNTRUSTED(eobj);
  • /* (1…100).each_cons(2) => “#<Enumerator: 1…100:each_cons(2)>” */
  • str = rb_sprintf("#<%s: ", cname);
  • rb_str_concat(str, rb_inspect(eobj));
  • rb_str_buf_cat2(str, “:”);
  • rb_str_buf_cat2(str, rb_id2name(e->meth));
  • if (e->args) {
  • int argc = RARRAY_LEN(e->args);
  • VALUE *argv = RARRAY_PTR(e->args);
  • rb_str_buf_cat2(str, “(”);
  • while (argc–) {
  •  VALUE arg = *argv++;
    
  •  rb_str_concat(str, rb_inspect(arg));
    
  •  rb_str_buf_cat2(str, argc > 0 ? ", " : ")");
    
  •  if (OBJ_TAINTED(arg)) tainted = Qtrue;
    
  •  if (OBJ_UNTRUSTED(arg)) untrusted = Qtrue;
    
  • }
  • }
  • rb_str_buf_cat2(str, “>”);
  • if (tainted) OBJ_TAINT(str);
  • if (untrusted) OBJ_UNTRUST(str);
  • return str;
    +}

+/*

    • call-seq:
    • e.inspect => string
    • Create a printable version of e.
  • */

+static VALUE
+enumerator_inspect(VALUE obj)
+{

  • return rb_exec_recursive(inspect_enumerator, obj, 0);
    +}

/*

  • Yielder
    */
    @@ -779,6 +841,7 @@ Init_Enumerator(void)
    rb_define_method(rb_cEnumerator, “with_object”,
    enumerator_with_object, 1);
    rb_define_method(rb_cEnumerator, “next”, enumerator_next, 0);
    rb_define_method(rb_cEnumerator, “rewind”, enumerator_rewind, 0);
  • rb_define_method(rb_cEnumerator, “inspect”, enumerator_inspect, 0);

    rb_eStopIteration = rb_define_class(“StopIteration”,
    rb_eIndexError);

e$B%A%1%C%He(B #810 e$B$,99?7$5$l$^$7$?!#e(B (by Akinori MUSHA)

e$B%9%F!<%?%9e(B Opene$B$+$ie(BClosede$B$KJQ99e(B
e$B?JD=e(B % 0e$B$+$ie(B100e$B$KJQ99e(B

Applied in changeset r20481.

http://redmine.ruby-lang.org/issues/show/810

At Wed, 3 Dec 2008 20:36:41 +0900,
Yusuke ENDOH wrote:

2008/12/03 19:57 Akinori MUSHA [email protected]:
(snip)

まあ、デバッグ用だから見せてしまってもいいかもしれませんね。

(1…100).each_cons(2) => “#<Enumerator: 1…100:each_cons(2)>”

という感じでどうでしょうか。(下に添付)

おお、要は「Enumerator の正体を p だけで (ある程度) 知りたい」という
ことなので、これで十分です。
. ではなく : なのがちょっと引っかかりますが、細かいことです。

 . だとRangeãªã©ã®å ´åˆç‰¹ã« Ruby の式っぽく見えるけれども、

  • inspect の結果なのでそもそも式ではない

  • 式のように見える分、 Range ã®å ´åˆãªã© () で括らないといけない
    気がして煩わしい

という感じがしたので : にしました。

s.rewind
性質 (つまり Array#each のようなの) が期待されている

  • それ自体が副作用を持つようなメソッドで Enumerator を作るのは自己責任

という雰囲気でした。蛇足でした。

そうですね。IO系のように、オブジェクト内部に走査中の位置が保持
されているものは rewind できていませんね。1.8 と 1.9 で挙動が
異なっているのもまずいかも。

オブジェクトの respond_to?(:rewind) が真だったら rewind を呼ぶ
ようにすべきなのかな。

e$B%A%1%C%He(B #818 e$B$,99?7$5$l$^$7$?!#e(B (by Akinori MUSHA)

e$B%9%F!<%?%9e(B Opene$B$+$ie(BClosede$B$KJQ99e(B

Committed on trunk.

http://redmine.ruby-lang.org/issues/show/818

At Thu, 4 Dec 2008 11:28:45 +0900,
I wrote:

At Wed, 3 Dec 2008 20:36:41 +0900,
Yusuke ENDOH wrote:
(snip)
異なっているのもまずいかも。

オブジェクトの respond_to?(:rewind) が真だったら rewind を呼ぶ
ようにすべきなのかな。

 そんな気がしたので、 trunk ではそのようにしました。

At Sat, 13 Dec 2008 00:05:00 +0900,
Yugui (Yuki S.) wrote:

きだと思いますか?
 IO系を enumeratorize ã—ãŸå ´åˆã« rewind が rewind してくれない
という意味では意図せざる挙動だったと言えなくもないですね。また、
rewind しても意味がなかったのでその挙動に積極的に期待することは
ないと推測され、あまり互換性上の問題にはならない気がします。

 rewind 自体がおそらくレアなので、厳密には非互換とはいえ、私は
マージしていいと思います。

Yuguie$B$G$9!#e(B

Akinori MUSHA e$B$5$s$O=q$-$^$7$?e(B:

e$B$=$&$G$9$M!#e(BIOe$B7O$N$h$&$K!"%*%V%8%’%/%HFbIt$KAv::Cf$N0LCV$,J];}e(B
e$B$5$l$F$$$k$b$N$Oe(B rewind e$B$G$-$F$$$^$;$s$M!#e(B1.8 e$B$He(B 1.9 e$B$G5sF0$,e(B
e$B0[$J$C$F$$$k$N$b$^$:$$$+$b!#e(B

e$B%*%V%8%’%/%H$Ne(B respond_to?(:rewind) e$B$,??$@$C$?$ie(B rewind e$B$r8F$Ve(B
e$B$h$&$K$9$Y$-$J$N$+$J!#e(B

e$B%P!<%8%g%s4V0l4S@-$G$$$&$H!“e(B1.9.1e$B$He(B1.9.2e$B$Ge(Brewinde$B$N5sF0$,0c$&$N$bG:$^$7$$e(B
e$B$H;W$$$^$9!#7k9=Bg$-$J;EMMJQ99$@$H;W$&$s$G$9$,!”$3$l!"e(B1.9.1e$B$K<h$j9~$`$Ye(B
e$B$-$@$H;W$$$^$9$+e(B?