CubicLouve

Spring_MTの技術ブログです。https://github.com/SpringMT (http://spring-mt.tumblr.com/ からの移転)

Rubyのお気持ちを読む

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が新たに定義され、そのローカル変数foobazメソッドに引数と渡されるため、最終的にnilが表示されることとなります。

ローカル変数の定義はリファレンスマニュアルにも明記されています。

変数と定数 (Ruby 2.4.0)

ただ、こういうRubyの挙動を忘れてしまったときはどのように判断すれば良いのでしょうか?

そういうときはRubyVMの命令シーケンスを確認することでRubyの挙動を追うことが可能です。

RubyVMの命令シーケンスを取得する

RubyVMの命令シーケンスを取得するには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

実行した結果はこんな感じになります。

fooがローカル変数になる

これだけではわかりにくいので、実際にメソッド呼び出しがされる場合も見てみましょう。

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

上記スクリプトの命令シーケンスはこんな感じです。

fooがメソッド呼び出しになる

わかりにくいので、diffを取ってみます。

gist.github.com

最初のスクリプトではローカル変数 fooが設定されていることが分かるかと思います。

Rubyの挙動で困ったときはVMの命令シーケンスを確認してみてはいかがでしょうか?