Ruby - Nokogiriによるスクレイピング(YahooFinance) - Qiita
のコメント欄を書いているときに、挙動の違いに改めて気付いたので挙げときます。
Enumerable#injectに限られる話ではないのですが、気付いたときに使っていたのがこのメソッドだったので。
##先に結論を
do…endで書くときには、返値確認用のpを直接使うな。
##Enumerable#inject
まず中括弧の場合。injectの例としてこちらから引用します。
ruby の inject をわかりやすく説明してみる - Λάδι Βιώσας
pry(main)> p (1..10).inject(0) {|sum, i| sum + i }
55
=> 55
初期値0をsumに入れて、返値sum + iをsumに戻して次のループへ、ってメソッドですね。
ここで使っている中括弧を単純にdo…endに変えます。
pry(main)> p (1..10).inject(0) do |sum, i|
pry(main)* sum + i
pry(main)* end
TypeError: 0 is not a symbol
from (pry):**:in `inject'
…え?
そうだよ「0はシンボルじゃない」よ。当然じゃないか。
なんでそんなエラーを返すの??
###るりまを確認
{ ... } の方が do ... end ブロックよりも強く結合します。次に例を挙げますが、このような違いが影響するコードは読み辛いので避けましょう:
foobar a, b do .. end # foobarの引数はa, bの値とブロック
foobar a, b { .. } # ブロックはメソッドbの引数、aの値とbの返り値とがfoobarの引数
つまり、括弧を明示すると
foobar(a, b) do .. end # foobarの引数はa, bの値とブロック
foobar(a, b { .. }) # ブロックはメソッドbの引数、aの値とbの返り値とがfoobarの引数
とRubyインタプリタは解釈します。
###考察
pを使うときには一般的に、引数を囲む括弧を用いない。
確認のために括弧を明示してみる。
p (1..10).inject(0) do |sum, i|
sum + i
end
上記のことを考慮すると
p((1..10).inject(0)) do |sum, i|
sum + i
end
と解釈されてそう。
pry(main)> p((1..10).inject(0)) do |sum, i|
pry(main)* sum + i
pry(main)* end
TypeError: 0 is not a symbol
from (pry):**:in `inject'
pry(main)> p((1..10).inject(0))
TypeError: 0 is not a symbol
from (pry):67:in `inject'
ビンゴ。
###るりまのEnumerable#injectを確認
inject(init = self.first) {|result, item| ... } -> object[permalink]
inject(sym) -> object
[PARAM] sym:
ブロックの代わりに使われるメソッド名を表す Symbol オブジェクトを指定します。 実行結果に対して sym という名前のメソッドが呼ばれます。
instance method Enumerable#inject
###理解
- do…endだと{…}よりも結合度が低いので、pを使うと
(1..10).inject(0)
までがpの引数として扱われた。 - そのためにinjectの引数が「Symbolじゃない」とエラーが出た。
- うしろに付いているdo…endブロックは無視されている。
##解決例
result = (1..10).inject(0) do |sum, i|
sum + i
end
p result
##まとめ
大事なことなので二度書きます。
do…endで書くときには、返値確認用のpを直接使うな。
##はてブコメントへの返答
###id: mattn
まとめが逆な気がした。「pを使う時は do end は使うな」?
Kernel.#pは基本的にデバッグ用のメソッドです。
つまり、ここでpを使ったのは 返値を一時的に知りたいから であり、最終的にpは削除します。
という心持ちでこのエントリを書いたので、do…endが主体になっています。
また、「直接」と書いたのは 返値を知りたいならば、一時変数に代入してからpで表示すべきであり、一時変数を省いてpを使うと痛い目に遭う という意味合いでした(分かりづらかったので「解決例」の段落を追加しました。
###id: ngsw
http://www.oki-osk.jp/esc/ruby/tut-07.html ここらと同じなのだろうか
ご指摘の通りです。
上記サイト
Ruby チュートリアル - 7. 微妙な問題
から一部を引用します。
7.5 do … end ブロックの優先順位
p [1,2,3].each {|x| puts x}, 4 # 無事に実行される
p [1,2,3].each do |x| puts x end, 4 # SyntaxError
…カンマが後続することは構文的にあり得ないから…
>```
p [1,2,3].each do |x| puts x end # LocalJumpError
なぜだろうか?…つまり,括弧で明示すると
p([1,2,3].each()) {|x| puts x} # LocalJumpError
であるかのように構文解析される。 たしかに SyntaxError ではないが,イテレータである each にブロックが渡されないから, LocalJumpError が発生する。
まさにdo…endと{…}との挙動の違いです。
>###id: otchy210
Rubyist では無いので詳しくないのだけど、よく色んな言語で見かける、&& と and の強さの違いみたいのが、ブロックでもあるっていう事か。
ご指摘のように、演算子の優先順序に近いものがあると思います。
* Rubyはメソッドの引数を明示する括弧を(意味が不明瞭にならない範囲で)省くことが出来る
* 標準出力へ出力するメソッド(puts, p, pp, print, …)では、括弧を省くのが通常の書き方
この二点が影響して(私的なミスが生じて)今回のエントリを書くことになったわけですw
>###id: tanakaBox
割と色々ある気がする。この場合、`(1..10).inject(0) do |sum, i|; sum + i; end.tap{|t| p t}`と、`end`の後に続ければいいんじゃね?
今回の目的を満足するためには、全くその通りです!知らなかったので感謝です!
気になることとしては、すこし話がずれるのですが、`end.tap`というふうに、endのあとにメソッドを続けることに違和感が生じること、でしょうか。(パーフェクトRuby pp.101-2にも、do…endと{…}の書き分け理由の一つに挙げられてました。
後日追記:
末尾に `Object#tap` メソッドを使わなくても `Object#display` メソッドがあるじゃないか。
[忘れられた出力メソッドObject#display - Qiita](https://qiita.com/riocampos/items/42d55c7a5dfcb69f05e4)