ブロック付きgsubの返り値の違いについて
基本的な話なのだがRubyのブロックには二通りの記法がある。
Objects.each { |object| object.method }
と
Objects.each do |object| object.method end
だ。
僕は、手続き上両記法は等価なものだと思っていたのだが、どうもそうでは無いらしい、ということが分かったのでメモとして残しておく。
違い
さて、どのようなケースで等価ではないかというと、下記のような時だ。
pry(main)> s = "hello, world" pry(main)> puts s.gsub(/\w+/) {|word| word.capitalize } Hello, World => nil
このコードをdo...endによる記法で書くと以下の様な挙動になる。
pry(main)> s = "hello, world" pry(main)> puts s.gsub(/\w+/) do |word| pry(main)* word.capitalize pry(main)* end #<Enumerator:0x000056474a99ef70> => nil
原因
原因は、{...}
とdo...end
の結合の強さの違いにある。
Ruby 2.4.0 リファレンスマニュアルには下記のようにある
{ ... } の方が do ... end ブロックよりも強く結合します。 次に例を挙げますが、このような違いが影響するコードは読み辛いので避けましょう:
foobar a, b do .. end # foobarの引数はa, bの値とブロック
foobar a, b { .. } # ブロックはメソッドbの引数、aの値とbの返り値とがfoobarの引数
つまり、
pry(main)> s = "hello, world" pry(main)> puts s.gsub(/\w+/) do |word| pry(main)* word.capitalize pry(main)* end #<Enumerator:0x000056474a99ef70> => nil
では、putsメソッドの引数はs.gsub(/\w+/)
とdo...end
ブロックと解釈されているのだ。
それぞれ、分けて実行してみると、以下のようになる
pry(main)> puts s.gsub(/\w+/) #<Enumerator:0x0000560d6a307a78> => nil
pry(main)> puts do pry(main)* s = 10 pry(main)* end => nil
なるほどこの2つが合わさっていたのか。
Rubyではブロックを取るメソッドをブロックを省略して書いた場合、Enumeratorオブジェクトを返す。 gsubの引数に正規表現パターンのみを与えた場合、ブロックを使っての置換の記法になるため、1つめはこれだろう。
2つめはなんだろうか。 チラッと調べてみた所、ブロックを取らないメソッド(内部にyieldがない)にブロックを渡した場合、ブロックは単に無視される、という話が引っかかった。 多分これなんじゃないだろうか。こっちについてはもう少し確認する必要がありそうだけれども。
バージョン
$ ruby --version ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-linux] => nil