2017/02/01

Recent entries from same category

  1. Ruby の Array#<< は Array#push よりも速いか
  2. Re: Ruby 製バッチ処理を省メモリ化した
  3. Crystal と CRuby でHTTPサーバのベンチマーク
  4. pure mruby な JSON パーサ書いた。
  5. bundle exec がウザい

以前からずっと疑問に思っていた事があった。

ruby の後置 if/unless で条件が偽になった場合でも代入構文が実行されるのはどうしてだろう

例えば以下のコードを irb や pry で実行してみて欲しい。

a = 1 if false

続けて a をタイプする。すると nil が表示される。

僕のこれまでの理解だと後置if/unlessは、ステートメントに作用するのでそのステートメント自体が無効になる、つまり代入自体されなかった事になるという理解だった。ruby のパーサのソースコードを見ても後置ifはステートメントに作用している様だった。

        | stmt modifier_if expr_value
            {
            /*%%%*/
            $$ = new_if($3, remove_begin($1), 0);
            fixpos($$$3);
            /*%
            $$ = dispatch2(if_mod, $3, $1);
            %*/
            }
だって raise "foo" if false で例外が飛ばないなら、代入もされないでしょと思っていたけど nil が代入される。この ruby のコードを AST ダンプするとこうなる。
# @ NODE_SCOPE (line: 1)
# +- nd_tbl: :a
# +- nd_args:
# |   (null node)
# +- nd_body:
#     @ NODE_PRELUDE (line: 1)
#     +- nd_head:
#     |   (null node)
#     +- nd_body:
#     |   @ NODE_IF (line: 1)
#     |   +- nd_cond:
#     |   |   @ NODE_FALSE (line: 1)
#     |   +- nd_body:
#     |   |   @ NODE_DASGN_CURR (line: 1)
#     |   |   +- nd_vid: :a
#     |   |   +- nd_value:
#     |   |       @ NODE_LIT (line: 1)
#     |   |       +- nd_lit: 1
#     |   +- nd_else:
#     |       (null node)
#     +- nd_compile_option:
#         +- coverage_enabled: true

これを見ると、AST に落とし込んだ時点でノードテーブルに a が現れている。つまり、後置ifが偽であろうとも代入構文を認識しているという事になる。この AST をどう walk しても代入構文には到達しないよなーと悩んでソースを見たりしたけど良く分からなかったので Matz に直接聞いた。

つまり ruby はこの stmt modifier_if expr_value を見つけると、まずステートメントが何かを判定して代入構文であれば変数 a を用意し、そのあと後置ifを判定して最後に代入という動きを取る。もちろん後置ifが偽であれば代入はされない。よって a は初期化されたままの nil が格納されるという事になる。これを理解した上で以下を見ると、なぜ NameError: undefined local variable or method `a' for main:Object ではなく NoMethodError: undefined method `+' for nil:NilClass なのか理解できた。なるほど深い。

irb(main):001:0> a = a + 1
NoMethodError: undefined method `+' for nil:NilClass
        from (irb):1
        from c:/msys64/mingw64/bin/irb.cmd:19:in `<main>'
irb(main):002:0>

blog comments powered by Disqus