【一人 bugs.ruby Advent Calendar 2020】[Bug #17058] Array#delete_if doesn't change array instantly【8日目】
一人 bugs.ruby Advent Calendar 2020 8日目の記事になります。
[Bug #17058] Array#delete_if doesn't change array instantly
さて Array#delete_if
で条件にあった要素を削除する事ができます。
a = [1, 2, 3, 4, 5, 6] # 偶数を削除する a.delete_if { |it| it.even? } p a # => [1, 3, 5]
このメソッドのドキュメントでは『ブロックが呼ばれるたびにレシーバに即座に反映される』と書かれています。
しかし、実際の挙動は #delete_if
の処理が終わったあとに反映されるような挙動になっています。
a = [1, 2, 3, 4, 5, 6] # 偶数を削除する a.delete_if { |it| puts "During iteration: #{a}" it.even? } p a # 期待する挙動: # During iteration: [1, 2, 3, 4, 5, 6] # During iteration: [1, 2, 3, 4, 5, 6] # During iteration: [1, 3, 4, 5, 6] # During iteration: [1, 3, 4, 5, 6] # During iteration: [1, 3, 5, 6] # During iteration: [1, 3, 5, 6] # [1, 3, 5] # 実際の動作: # During iteration: [1, 2, 3, 4, 5, 6] # During iteration: [1, 2, 3, 4, 5, 6] # During iteration: [1, 2, 3, 4, 5, 6] # During iteration: [1, 3, 3, 4, 5, 6] # During iteration: [1, 3, 3, 4, 5, 6] # During iteration: [1, 3, 5, 4, 5, 6] # [1, 3, 5]
ちょっとわかりづらいんですが、上の例の実際の動作では『即座に要素を削除していく』のではなくて『配列の先頭から結果をどんどん書き換えていく』というような動作になっています。
この挙動は Ruby 2.2 -> 2.3 で変更されたようです。
このチケットでは『挙動を直す』のではなくて『ドキュメントを修正する』という対応がなされました。
『レシーバを変更中にレシーバを参照するのはよくない』っていうやつですねー。
普段こんなコードを書かないんですが、実際にレシーバの中身を見てみると Ruby がどんな処理を行っているのか垣間見る事ができて面白いですね。