Ruby のメソッド間で変数を共有する

Ruby のメソッド間では、ふつうは変数は共有されません。

def counter_set
  x = 0
end

def inc
  x += 1
end

def counter_value
  x
end

counter_set
inc
inc
counter_value

は最初の inc 呼び出しのところで

undefined method `+' for nil:NilClass (NoMethodError)

のエラーが出ます。これは Ruby の仕様で、却ってメソッドは安全ともいえるわけです。もし変数を共有したければ、メソッドではなく、クロージャである proc を使えば事足ります。

x = 0

inc = proc do
  x += 1
end

inc.call
inc.call
x          #=>2

という具合に。


しかし、これでは「イヤだ」、メソッドを使いたいと仰るわがままな方もおられましょう。これはじつは、Rubyメタプログラミングを使えば可能です。

def counter_set
  x = 0
  
  define_method(:inc) do
    x += 1
  end
  
  define_method(:counter_value) do
    x
  end
end

counter_set
inc
inc
counter_value    #=>2

こんな具合ですね。Module#define_method のメソッド定義がブロック(Ruby のブロックはクロージャです)で行われているために、変数 x が透過して保持されます。

同様のことは、メソッドだけでなくクラスやモジュールでも可能です。


メタプログラミングRuby 第2版

メタプログラミングRuby 第2版


なお、上のはなんちゃってメソッドですが、もし冗談にせよ使うなら、こんな感じの方がいいかも知れません。

module Kernel
  def counter_set
    x = 0
    Kernel.send(:define_method, :inc) do
      x += 1
    end
    
    Kernel.send(:define_method, :counter_value) do
      x
    end
  end
end

これなら、クラスの中でなど、どこでも使えます。例えばこんな感じ。

counter_set

class A
  inc
  def initialize
    inc
  end
end

counter_value    #=>1
A.new
counter_value    #=>2