scramble cadenza

技術ネタのガラクタ置き場

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

マニアック過ぎてもう二度と使う気がしないけど、便利なオプションです。

参考