Edited at

実践!メタプログラミングRuby #オープンクラス編


はじめに

最近コツコツとメタプログラミングRubyを読んでいます。

抽象的な所が多いので、勉強した所を手を動かしながら定期的にまとめていこうと思います。


前提知識


  • Rubyの基本的な知識(あればベストですが、なくても調べながら読めば大丈夫です)


この記事で習得できること


  • オープンクラス


    • モンキーパッチ

    • refinements




内容


オープンクラス


  • 既存のクラスを再定義することで、その場でメソッドの追加・修正ができる

一言でいうとこんな感じですね。

まずはオープンクラスを使わない形で実装してみましょう。

例えば文字列の語尾に'ruby'という文字列を追加するadd_rubyメソッドを作りたいとします。

まずファイルを作り、メソッドを作成します。


matapro.rb

def add_ruby(str)

str + 'ruby'
end

irbを立ち上げ、実際に先ほど作成したメソッドを使用してみましょう。

irb(main):001:0> require './metapro'

=> true
irb(main):002:0> add_ruby("hoge")
=> "hogeruby"

このような感じになると思います。

この実装でも良いのですが、このadd_rubyは文字列で使える汎用的な機能です。

ですので、文字列を外部のメソッドに渡すのではなく、文字列自身に処理してもらった方がベターでしょう。

ここで、オープンクラスを使って行きましょう。

Stringクラスをオープンして、add_rubyメソッドを追加していきます。


matapro.rb

class String

def add_ruby
self + 'ruby'
end
end

こうすることで、文字列自身に処理してもらうことが可能です。

irb(main):001:0> require './metapro'

=> true
irb(main):002:0> "hoge".add_ruby
=> "hogeruby"

このようにオープンクラスはとても便利なのですが、1つ気をつけなければいけないことがあります。

それが、モンキーパッチです。


モンキーパッチ


  • 既に定義されているメソッドを再定義することで既存メソッドが更新されること

コードで言うとこんな感じですね。


metapro.rb

class String

def empty?
nil
end
end

Stringクラスにはもう既にempty?メソッドが定義されていますが、独自にempty?メソッドを定義した時に上書きされています。

そして元のメソッドに依存しているコードがあると、予期せぬエラーが発生します。

irb(main):001:0> require './metapro'

=> true
irb(main):002:0> "hello".empty?
=> nil #falseが欲しいのに、モンキーパッチでnilになってしまっている

モンキーパッチには意図的に起こすものと、意図せずに起こるものの2つがあると考えて良いと思います。

前者は良いのですが、後者は先ほどのように、予期せぬエラーが発生しますので、注意が必要です。

前者の対策としてRefinementsを使うことができます。


Refinements


  • モジュール定義の中でrefineを使用することで、限定されたスコープの中でオープンクラスを使用できる

実際に使ってみましょう。まず、コードを書きかえてください。


metapro.rb

module StringExtension

refine String do
def add_ruby
self + 'ruby'
end
end
end

そして、irbを立ち上げましょう。

$ irb --context-mode=1

irb(main):001:0> require './metapro'
=> true

irb(main):002:0> "hoge".add_ruby #ここではusingしていないのでadd_rubyが使えない
NoMethodError (undefined method `add_ruby' for "hoge":String)

irb(main):003:0> using StringExtensions #usingで有効化
=> main

irb(main):004:0> "hoge".add_ruby #add_rubyが使えるようになる
=> "hogeruby"

このようにrefineとusingを使うことで、有効範囲を絞った上でオープンクラスを使用することができます。

refinementsが有効になるのは、refineブロックそのものと、usingを呼び出した所からモジュールの終わり、またはファイルの終わりまでです。


まとめ



  • オープンクラスを使うことで標準のStringクラス等にもメソッドを追加できる



    • オープンクラス=>既存のクラスを再定義することで、その場でメソッドの追加・修正ができること




  • オープンクラスを使う時は、モンキーパッチに注意する



    • モンキーパッチ=>既に定義されているメソッドを再定義することで既存メソッドが更新されること




  • refinementsを使うことで有効範囲を絞った上でオープンクラスを使用することができる



    • refinements=>モジュール定義の中でrefineを使用することで、限定されたスコープの中でオープンクラスを使用できる