Ruby
メタプログラミング

[Ruby] メタプログラミングの入り口、オープンクラスを理解する

More than 3 years have passed since last update.

概要

メタプログラミングRuby勉強録。

今回のトピックはメタプログラミングの代名詞の一つ、オープンクラスです。

オープンクラスとは

既存するクラスを好きな場所で再オープンし、
メソッド修正・追加など任意の変更を加えられる機能のこと。

例えば

以下のような文字列操作メソッドがあったとします。

def love_ruby(str)
  str + ' I love Ruby!' 
end

love_ruby('My name is kidachi.')
=> "My name is kidachi. I love Ruby!"

取りあえずトップレベルに定義しましたが、
オブジェクティブにするため、クラスに所属するようにしましょう。
そこで、オープンクラスを使ってStringクラス自体を改変します。

class String
  def love_ruby
    self + ' I love Ruby!'
  end
end

'My name is kidachi.'.love_ruby
=> "My name is kidachi. I love Ruby!"

これでStringクラスを改変できました。
以後、あらゆる文字列がlove_rubyメソッドを利用できます。

'Foo!'.love_ruby
=> "Foo! I love Ruby!"

'Bar!'.love_ruby
=> "Bar! I love Ruby!"

'I love Ruby!'.love_ruby
=> "I love Ruby! I love Ruby!"

これがオープンクラスの力です。

また、新規メソッドを定義するだけでなく、
既存のメソッドを書き換えてしまうことも可能です。

例えば、文字列操作でよく使うupcaseを書き換えてみましょう。

# 本来のupcase
'I love Ruby!'.upcase
=> "I LOVE RUBY!"

# オープンクラスによりupcase自体を書き換え
class String
  def upcase
    self + ' I love Ruby!'
  end
end

# 改変後のupcase
'I love Ruby!'.upcase
=> "I love Ruby! I love Ruby!"

いかがでしょうか。
ここまでで、その強力さと同時に危険さが伝わったかと思います。

いくらRubyのことが好きでも、大文字変換が期待されるメソッドを
まったく関係のない機能で上書いてしまうのは、ただのテロですよね。

つまり、オープンクラスは、
その気になればStringやArrayと言ったコアなクラスも
思いのままに破壊できてしまう危険性

を伴っているということ。

よってオープンクラスを使う際は以下のルールを守るべきです。

  • 全ての文字列が保持しても問題のない汎用的な機能か見極める
  • 充分にテストする

つまり上記例で言うと、upcaseの書き換えはもちろん、
汎用性に欠けるlove_rubyメソッドも本来オープンクラスで
追加するようなものではないということですね。

代替案

love_rubyメソッドのように、限定的な場面でのみ
使いたい/使われるべき機能の場合は、

  • 大人しく拡張Stringクラスを定義する
  • (Ruby2.0以上の場合は)refinmentsを用いる

という選択肢があります。

※refinments
オープンクラスの影響を特定範囲に限定出来る。
ただし、Ruby2.0で実験的実装、2.1で正式採用という
非常に新しい機能なので、利用の際は最新情報キャッチアップの上でどうぞ!

参考)Refinementsとは何だったのか
http://magazine.rubyist.net/?0041-200Special-refinement

モンキーパッチ

オープンクラスにより既存のメソッドを置き換えることモンキーパッチと言います。

また、特に、意図せずに既存メソッドを書き換えてしまった事象のことや、
それゆえにマイナスイメージを伴うものを意味する向きもあります。

もちろん普通に便利なものとして使うことも。
明確な定義はなく、細かいところはその場の文脈で変わる様です。

上記のString#upcaseの書き換えは(良くない)モンキーパッチですね。

結論

オープンクラス(に限らずメタプログラミング全般)は

  • 既存クラスをいつでも思いのままに修正出来る利便性
  • ルールを破壊してしまう危険性

を備えています。

メタプログラミングの入り口に立った私たちは、
今後常にMatzのことばを頭の片隅に置いておきたいですね。

「Rubyは君を信頼する。Rubyは君を分別のあるプログラマとして扱う。
Rubyはメタプログラミングのような強力な力を与える。
ただし、大いなる力には、大いなる責任が伴うことを忘れてはいけない」

-「メタプログラミングRuby」序文より。

以下に続く

[Ruby] method_missing()を実用レベルで理解する
http://qiita.com/kidachi_/items/75ae4a29c99a79816384