雑にRefinementsが使えるようにしてみた

経緯

最近、「Rubyにこういうのあったら便利かなぁ」と思ったメソッドをrstdというgemに追加して個人プロジェクトとかで使えるようにするのがマイブームになっています。

github.com

で、色々実装している時に、サクッとRefinementsが使えないだろうかと思い、実装してみた記事になります。

やりたかったこと

以下のようなコードで特定のクラス内などでサクッとRefinementsが使いたかった。

class A
    using refine_method(Integer, :inc, ->() {self + 1})

    def inc
      puts 41.inc
    end
end

A.new.inc
# => 42  

実装

で、実際の実装は以下のような感じです。

module RefineKernel
  refine Kernel do
    def refine_method(klass, method_id, expr)
      Module.new{
        refine klass do
          define_method method_id.to_sym, expr
        end
      }
    end
  end
end

using RefineKernel

class A
    using refine_method(Integer, :inc, ->() {self + 1})

    def inc
      puts 41.inc
    end
end

A.new.inc
# => 42  

やってること

module RefineKernel
  # Kernel で refine_method が使えるようにする
  refine Kernel do
    def refine_method(klass, method_id, expr)
      Module.new{
        refine klass do
          define_method method_id.to_sym, expr
        end
      }
    end
  end
end

Kernelrefine_method というメソッドが使えるようにしています。
またrefine_method は引数として klassmethod_idexprを受け取ります。

klassはRefinementsでメソッドを追加したいクラスを引数として渡しており、method_id が追加するメソッド名、expr がメソッドの実装となっています。

あとはusing RefineKernelすれば、以下のようにRefinementsを使ってメソッドを追加し、利用することができます。

using RefineKernel

using refine_method(Integer, :inc, ->() {self + 1})

p 41.inc
# => 42

ちなみに以下のような感じでも動作するので意外と便利かも……?

using refine_method(Array, :pow, ->(num = 2){ self.map{|v| v ** num }})

ary = [1, 2, 3]

puts ary.pow
# => [1, 4, 9]

puts ary.pow(3)
# => 1, 8, 27

using refine_method(Object, :xbox, ->(title = "HALO", *args){
  puts title if args.size == 0
  args.each do |arg|
    puts "#{title} #{arg}"
  end
})

obj = Object.new

obj.xbox
# => HALO

obj.xbox("HALO", "CE", 2, 3)
# => HALO CE
# => HALO 2
# => HALO 3

obj.xbox("Devil May Cry", "", "2", "4", "5")
# => Devil May Cry 
# => Devil May Cry 2
# => Devil May Cry 4
# => Devil May Cry 5

ただ、一度に一つのメソッドしか定義できないのが不満かな。 そこらへんはいい感じにHashオブジェクト内にSymbolとProcを格納してmapとかで回せばいいのかもしれないけど。

実際のPRとか

github.com

github.com