Project

General

Profile

Bug #7493

ブロックを渡された場合最初の1要素のみを返すEnumeratorに対してnextを送り続けると、2度目にStopIteration例外が発生する

Added by kachick (Kenichi Kamiya) almost 8 years ago. Updated almost 8 years ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Target version:
ruby -v:
ruby 1.8.7 (2012-10-12 patchlevel 371) [x86_64-linux]
[ruby-dev:46684]

Description

「ブロック付きで動かした場合、最初に見つかった1要素のみを返すメソッド」からEnumeratorを作ると、
2度目のnextでStopIteration例外を返すようです。
思い至った限りで確認したメソッドは、以下の物です。

  • Array#index
  • Array#find_index
  • Array#rindex
  • Enumerable#detect
  • Enumerable#find

これは、1.8.7と1.9間の仕様に於ける差異と考えた方が良いのでしょうか?
生成されたEnumeratorに対してto_aを送ると全要素を含むArrayが返る事から、
1.8.7に於いても全要素走査出来る方が自然では無いかと感じました。

to_aとの差異は、dbussinkの指摘で気づくことが出来ました。

https://github.com/rubinius/rubinius/pull/2063#issuecomment-10881875

array = [:a, :b, :c]
index_enum = array.index
index_enum.to_a #=> [:a, :b, :c]
index_enum.next #=> :a
index_enum.next #=> StopIteration: iteration reached at end


Files

CRuby1.8.7.rb (905 Bytes) CRuby1.8.7.rb CRuby-1.8.7 kachick (Kenichi Kamiya), 12/02/2012 01:35 PM
Other.rb (1007 Bytes) Other.rb Other kachick (Kenichi Kamiya), 12/02/2012 01:35 PM

Updated by knu (Akinori MUSHA) almost 8 years ago

1.8では、nextは lib/generator.rb で実装されています。

ブロック(の返り値)を欲するメソッドのEnumeratorをnextで回すというのは、つまり必要なブロックを渡していないわけで、そういう使い方は1.8では想定していませんでした。
つまり、事務的な答えとしては、挙動は不定ですということになります。

ただ、空のブロック({})の評価値がnilになることを考えると、1.9以降のようにnilを返してやるのが自然と言えるでしょうね。
Enumerable#to_a も、内部ではnilを返すブロックで繰り返しています。

直すとしたらこうですが、1.8.7はこの類の修正が入ることはもうないでしょうね。
気になるようであれば、ローカルで当てたりモンキーパッチしてみてください。
一応 ruby_1_8 には入れておきます。

Index: lib/generator.rb

--- lib/generator.rb (revision 38134)
+++ lib/generator.rb (working copy)
@@ -69,7 +69,7 @@ class Generator
def initialize(enum = nil, &block)
if enum
@block = proc { |g|

  • enum.each { |x| g.yield x }
  • enum.each { |x| g.yield x; nil } } else @block = block
#2

Updated by knu (Akinori MUSHA) almost 8 years ago

  • Status changed from Open to Closed
  • % Done changed from 0 to 100

This issue was solved with changeset r38145.
Kenichi, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


Make an internal block of Generator return nil instead of self.

  • lib/generator.rb (Generator#initialize): Make an internal block return nil instead of self. [Bug #7493]

Updated by knu (Akinori MUSHA) almost 8 years ago

あと、nextが割り込む位置も1.8と1.9以降では違っています。
破壊的なメソッドを例に取ると分かりやすいですが、

a=[1,2,3]
e=a.map!
p e.next #=> 1
p e.next #=> 2
p a #=> nil, nil, 3 nil, 2, 3

と、1.8ではnextで値が返ってくるのはyieldによるブロック呼出が終了した後(というか次のブロック呼出があった時)ですが、
1.9以降では元のメソッドがyieldした直後のタイミングで値が返って来、次のnextで元のメソッドに制御が戻るようになっています。
(1.9以降の挙動が望ましいと思います)

Updated by kachick (Kenichi Kamiya) almost 8 years ago

  • 頂いたパッチを反映させた後、期待通りの動作になることを確認しました。
  • 1.8.7で本パッチの取り込まれる可能性が低いという点について理解出来ました。

また、1.8と1.9+でのnext割込箇所の差異はこれまで意識したことがありませんでした。
空ブロックの評価値から設計の方向性を判断する点等、大変勉強になります。

御対応・御教示の程、有難うございました。

Also available in: Atom PDF