Rubyのお気持ちが分からないことがたまにありますよね。
このスクリプトで最終的になにが表示されるでしょうか。
class A attr_accessor :foo def bar foo = baz(foo) foo end def baz(v) v end end a = A.new a.foo = 1 p a.bar # ここで表示される値は?
答えは 1
ではなく nil
です。
barメソッドの foo = baz(foo)
において、foo =
がfoo=
メソッド呼び出しではなく、ローカル変数 foo
への代入とみなされて、
ローカル変数foo
が新たに定義され、そのローカル変数foo
がbaz
メソッドに引数と渡されるため、最終的にnil
が表示されることとなります。
ローカル変数の定義はリファレンスマニュアルにも明記されています。
ただ、こういうRubyの挙動を忘れてしまったときはどのように判断すれば良いのでしょうか?
そういうときはRubyのVMの命令シーケンスを確認することでRubyの挙動を追うことが可能です。
RubyのVMの命令シーケンスを取得する
RubyのVMの命令シーケンスを取得するにはRubyVM::InstructionSequence
クラスを使います。
https://docs.ruby-lang.org/ja/latest/class/RubyVM=3a=3aInstructionSequence.html
今回試したスクリプトをsample.rbとして保存し、そのファイルを読み込んで命令シーケンスを出力させてみます。
出力させるには下記のスクリプトを実行します。
puts RubyVM::InstructionSequence.compile_file("./sample.rb", true).disasm
実行した結果はこんな感じになります。
これだけではわかりにくいので、実際にメソッド呼び出しがされる場合も見てみましょう。
class A attr_accessor :foo def bar test = baz(foo) test end def baz(v) v end end a = A.new a.foo = 1 p a.bar
上記スクリプトの命令シーケンスはこんな感じです。
わかりにくいので、diffを取ってみます。
最初のスクリプトではローカル変数 foo
が設定されていることが分かるかと思います。