こんにちは。寺岡です。
前回の予告通り、今回はRuby2.0のRefinementsについて書きたいと思います。
Ruby2.0.0で実験的機能として追加されるRefinementsは、対象のクラスのメソッドを部分的に変更、追加するための機能です。
ruby2.0.0-rc2のインストール
まずは実行環境の準備です。
rvmインストール済の環境にRuby 2.0.0最後のRC版であるruby-2.0.0-rc2をインストールします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$ rvm get head $ rvm get latest $ rvm list known | grep '\[ruby-\]' [ruby-]1.8.6[-p420] [ruby-]1.8.7[-p371] [ruby-]1.9.1[-p431] [ruby-]1.9.2[-p320] [ruby-]1.9.3-p125 [ruby-]1.9.3-p194 [ruby-]1.9.3-p286 [ruby-]1.9.3-[p327] [ruby-]1.9.3-head [ruby-]2.0.0-rc1 [ruby-]2.0.0-rc2 $ rvm install 2.0.0-rc2 $ rvm use ruby-2.0.0-rc2 $ ruby -v ruby 2.0.0dev (2013-02-08 trunk 39161) [x86_64-linux] |
Refinementsを使ってみる
Refinementsの書式は以下となります
1 2 3 4 5 6 7 8 9 10 11 |
# Refinementsの定義 module Refinementsを定義するモジュール refine 対象のクラス do def 追加・変更したいメソッド(...) # メソッドの処理 end end end # Refinementsの利用 using Refinementsが定義されたモジュール |
簡単な例として、Stringクラスにhogerizeメソッドを追加してみます。
refineメソッドを呼び出してRefinementsを定義するとwarningが出ますが、気にせずに実行してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
module HogerizeString refine String do def hogerize self + " hoge" end end end # warning: Refinements are experimental, and the behavior may change in future versions of Ruby! foo = "foo" p foo.hogerize # NoMethodError: undefined method `hogerize' for "foo":String using HogerizeString p foo.hogerize # "foo hoge" |
using HogerizeStringを実行することで、Stringクラスにhogerizeメソッドを追加することができました。
RefinementsでInteger#/を書き換えてみる
今度は既存のクラスのメソッドを書き換えてみましょう。
rubyでは割り算のオペランドがどちらも整数の場合戻り値は整数になります。
小数の結果を得たいときは、どちらかのオペランドをto_fで浮動小数点数に変換してから呼び出します。
1 2 |
p 1 / 2 # 0 p 1.to_f / 2 # 0.5 |
いちいちto_fするのは面倒なので、Refinementsを使って書き換えてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
module FloatDiv refine Integer do def /(other) to_f / other end def div(other) to_f / other end end end using FloatDiv p 1 / 2 # 0 |
……上手くいきませんでした。
Rubyの整数型は、抽象クラスIntegerを継承したFixnumとBignumに分かれており、
Refinementsで定義されたメソッドの優先順位はサブクラスよりも低いため、Integerに対してrefineで定義しても、Fixnumの/メソッドが呼び出されてしまうためです。
気を取り直してFixnumとBignumをrefineするように修正して実行してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
module FloatDiv refine Fixnum do def /(other) to_f / other end def div(other) to_f / other end end refine Bignum do def /(other) to_f / other end def div(other) to_f / other end end end p 1 / 2 # 0 using FloatDiv p 1 / 2 # 0.5 |
今度は狙い通りに動きましたね!
Refinementsのスコープについて
Refinementsを利用するためのusingメソッドは、トップレベルスコープでのみ実行できます。
メソッドの中や、クラス、モジュールの宣言中は利用できません。
また、usingの有効範囲は「同一ファイル内のusing以降のコード」となっています。
Refinementsのスコープ周りはなかなか複雑なので、詳しくは本家のwikiを御覧ください。
https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/RefinementsSpec#Scope-of-refinements
まとめ
今回はRefinementsをちょっとだけ試してみました。
将来的には現在の静的なファイル単位のスコープではなく、もっと動的な機能へ進化してくれるだろうと期待しています。
現在はexperimental(実験的機能)として実装されているため本番のコードでは利用すべきではありません。
しかし、特定の範囲に選択的にmixinを実現する非常に先進的な機能なので、将来のためにRefinementsの賢い使い道を研究しておくと良いかもしれません。