ruby 経験者が90%以上間違える問題
イントロ
弊社の凄腕エンジニアが、タイトルに書いてあることを言いながらメンバーに出してくれた問題です。
もちろん私は 90 % 側ですた。
問題
以下の出力結果はどうなるでしょう?
p { :a => 1, :b => 2 }
Hash が出力されて Hash が返る、と思ったあなたは私と同じですw
結果は?
p { :a => 1, :b => 2 } SyntaxError: unexpected =>, expecting '}' p { :a => 1, :b => 2 } ^
構文エラーになりました。
実験
では以下の結果はどうなるでしょうか?
p ({ :a => 1, :b => 2 }) p { { :a => 1, :b => 2 } } p {} p {} {{}}
irb でやってみましょう。
考察
何で SyntaxError が出るか、という理由を説明する前に、実験の三番目の問題を考えてみます。
p {}
結果は以下です。
p {}
#=> nil
なんで nil なんだろう?と思いませんでしたか?
私の最初の予想は {} (空ハッシュ) が返ることでした。
一瞬 {} は Proc オブジェクトにも見えるなーと思ったのですが、その線で考えても、この挙動にはならないと思います。
何故ならば pメソッド は引数に渡されたオブジェクトを inspect して返すからです。当然 Proc#inspect みたいなメソッドはありません。
結果だけを見ると {}.inspect の返り値が nil になることを意味しています。
ところが確かめてみると
{}.inspect
#=> "{}"
となります。(当たり前だけど)
つまり
p {}
を p の引数を inspect して返す、と見ると nil が返るのは辻褄が合わない。
だけどそういう風にしか見えない... となるわけです。
さて、何故 nil になるのだろうか?
結論
p {}
×: p に空ハッシュが引数として渡る
○: p にブロックが引数として渡っている
がカラクリです。
p メソッドにブロックを渡しても当然無視され、inspect の対象となるオブジェクトは何も渡ってこないとみなされる。
だから nil が返ってくるんですね。
注: ruby のメソッドは内部で明示されない限り(yield などで) ブロックを渡しても渡さなくてもエラーにならない という特性がある
おまけ
つまり最初の問題
p { :a => 1, :b => 2 }
はブロック内部の式が不正な文法だから SyntaxError になる。が答えです。(多分)
ところで考察の部分で頭を悩ませたわけだが、以下で気づけた。
% echo "p {}" > hoge % ruby --dump parsetree hoge ########################################################### ## Do NOT use this node dump for any purpose other than ## ## debug and research. Compatibility is not guaranteed. ## ########################################################### # @ NODE_SCOPE (line: 1) # +- nd_tbl: (empty) # +- nd_args: # | (null node) # +- nd_body: # @ NODE_ITER (line: 1) # +- nd_iter: # | @ NODE_FCALL (line: 1) # | +- nd_mid: :p # | +- nd_args: # | (null node) # +- nd_body: # @ NODE_SCOPE (line: 1) # +- nd_tbl: (empty) # +- nd_args: # | (null node) # +- nd_body: # @ NODE_BEGIN (line: 1) # +- nd_body: # (null node)
- p メソッドの引数が null node になっている
- nd_body にもう一個 scope が作られている(?)
を見て、あーなるほど!そういうことか、とw
マニアック過ぎてもう二度と使う気がしないけど、便利なオプションです。