はじめに
Refinementsで遊んでみた記事です。具体的な内容としてはトップレベルでRefinementsが使えないかなと試してみたとき備忘録です。
Refinementsとは
Rubyでは、後から自由にクラスにメソッドを追加することができる。
class Integer def hoge puts :hoge end end 42.hoge # => :hoge
これはこれで便利なんだけど、グローバルに変更されるし、継承とかでも意図しない動きをする可能性があるのであまりいいとは言えない。
そこでRefinementsの出番。
moduel Refine refine Integer do def hoge p :hoge end end end # 42.hoge # => Error! using Refine 42.hoge # => :hoge
Refinementsではusing
した移行で再定義したものを使える。
またusing
した箇所のファイルスコープに限定されるので影響範囲が小さくて済む。
やりたかったこと
以下のようにトップレベルでrefine
を使って特定のクラスにメソッドを生やしたかった(毎回、using
とか書くのが面倒だったので……)
refine Integer do def hoge puts :hoge end end 42.hoge
ただ、このコードはrefine
というメソッドが定義されていないのでエラーになる。
やってみた
で、出来そうか試してみたところ以下のコードで動作した。
module Kernel module_function def refine klass, &block Module.new do refine klass do yield end end end end refine Integer do public def hoge p :hoge end end 42.hoge # => :hoge
やっていること
Refinementsをよく使われる人は以下のようなコードで無名モジュールを使っているんじゃないかと思う。
using Module.new { refine Integer do def hoge p :hoge end end } 42.hoge # => :hoge
このコードを元にKernel
モジュールにrefine
メソッドを作成。
module Kernel module_function def refine klass, &block Module.new do refine klass do yield end end end end
refine
メソッドは第一引数に再定義したいクラスなどを受け取り、メソッドの再定義などはブロック引数で渡します。
これにより以下のようにhoge
メソッドを追加できます。
refine Integer do public def hoge puts :hoge end end 42.hoge # => :hoge
ただし……
ただ、このコードはusing
していないのに再定義した内容が使用できているんですよね……。
module Kernel module_function def refine klass, &block Module.new do refine klass do yield end end end end # ここで usingを使っていないが作成したhogeメソッドが使えている…… refine Integer do public def hoge p :hoge end end 42.hoge # => :hoge
これはRefinementsの仕様通りの動きなのかがはっきりとしていないのでなぜ動いているのやら……。
それと今回のコードではpublic
を追加しないとメソッドが呼び出されなかった。
module Kernel module_function def refine klass, &block Module.new do refine klass do yield end end end end refine Integer do def hoge p :hoge end end 42.hoge # => private method `hoge' called for 42:Integer (NoMethodError)
エラーからprivate
メソッドとして定義されているらしいことがわかったのでpublic
を付けてみたら動いたという……。
この辺も仕様通りの動きなんだろうか……?
おわりに
とりあえず、やりたいことはできた。 これが仕様通りの動きなのかは判断がつかないので、Refinementsの実装とか読んでみようかと思う。