Ruby のメタプログラミングでカウンターを作る

Ruby はインクリメントやデクリメントがないのですが、遊びでカウンターを作ってみました。メタプログラミングを使って、呼び出されるたびに step(デフォルトは 1)だけ増える数を与えるメソッドを生成します。「メソッド」というのが工夫したところです。

使い方は、Counter.make(:inc) でカウンター inc を作成します。カウンターの名前は何でも構いません。デフォルトの初期値は 0、step は 1です。

Counter.make(:inc)
5.times {puts inc}    #=>0 1 2 3 4

Counter.make(:count, 100, -5)
puts count    #=>100
puts count    #=>95
puts count    #=>90

puts inc      #=>5

こんな感じです。特徴としては、トップレベルのメソッドとして定義されるので、クラスのインスタンス・メソッドの中などへ飛んでもカウントが持続します。また、どこで Counter.make しても構いません。
 カウンターが引数を取ると、step がその値に変わります。なので、カウントを進めずに値だけ知りたいときは、引数を inc(0) のようにすれば可能です。引数の効果はその場限りで、あとは最初に与えられた step が使われます。

class Nya
  def hoge
    puts count
  end
end

Counter.make(:count)
puts count       #=>0
Nya.new.hoge     #=>1
puts count(6)    #=>7
puts count       #=>8

ちょっと注意しておくと、初期値のデフォルトは 0 なので、例えば10回カウントしてカウンターは 9 になりますが、最後に値を取り出すときにもう一度呼べばカウンターは 10 を返すことになります。あるいは初期値を 1 にして 10回カウントし、引数 0 で値を取り出してもいいです。

Counter.make(:count)
10.times {count}
puts count       #=>10

Counter.make(:count, 1)
10.times {count}
puts count(0)    #=>10

 

コードは以下。

class Counter
  def self.make(mname, counter = 0, step = 1)
    f = true
    counter -= step
    Object.class_eval do
      define_method(mname) do |*arg|
        if arg.size >= 1
          counter += step if f
          counter += arg[0]
        else
          counter += step
        end
        f = false
        counter
      end
    end
  end
end