Ruby
メタプログラミング

はじめてのメタプログラミング

More than 1 year has passed since last update.

はじめに

メタプログラミングRubyを読んだので個人的に面白いなーって思ったものを纏めてます。

⚠メタプログラミングは魔術のように色々動的な処理に書き換える事が出来ますが、コードの可読性の低下致命的なバグを生み出す可能性があります。

使う際(特にチーム開発)は注意してください。

.sendを使った動的ディスパッチ

.sendメソッドを使うことで動的にメソッド呼び出すことが出来ます。

通常のメソッドの呼び出しの場合は

通常の呼び出し
# クラスの作成
class Hoge
  def huga(a)
    p "huga#{a}"
  end

  def piyo(b)
    p "piyo#{b}"
  end
end

# メソッドの呼び出し
hoge = Hoge.new
hoge.huga("huga")
# => hugahuga

.sendを使うと以下のように記述できます

.sendを使ったメソッドの呼び出し
# クラスの作成
class Hoge
  def huga(a)
    p "huga#{a}"
  end

  def piyo(b)
    p "piyo#{b}"
  end
end

# .sendを使ったメソッドの呼び出し
hoge = Hoge.new
hoge.send("huga", "huga")
# => hugahuga

上記のように第一引数にメソッド名に文字列としてメソッド名を定義することでメソッドを呼び出すことが出来ます。

evalメソッドを使った動的な処理

evalはメタプログラミングでも一番強力で素直なメソッドです。その分自由度が高いので取り扱いには注意してください。

先ほどの例をeval使って表現します。

通常の呼び出し
# クラスの作成
class Hoge
  def huga(a)
    p "huga#{a}"
  end

  def piyo(b)
    p "piyo#{b}"
  end
end

# メソッドの呼び出し
hoge = Hoge.new
hoge.huga("huga")
# => hugahuga
evalを使った呼び出し
# クラスの作成
class Hoge
  def huga(a)
    p "huga#{a}"
  end

  def piyo(b)
    p "piyo#{b}"
  end
end

# メソッドの呼び出し
# クラスをevalを使って動的に呼び出す
hoge = eval("Hoge").new
hoge.huga("huga")
# => hugahuga

上記はevalを使ってクラスを呼び出しました。evalは文字列を処理に置き換える事が出来るのです。

オープンクラスとモンキーパッチ

オープンクラスとは?

継承・オーバライド等を使わずに既存のクラスを再オープンし、編集を行うことです。
コレを使うことで、Rubyの標準クラスさえも後から変更することが出来ます。

モンキーパッチとは?

既存のクラスのメソッドの振る舞いを変更することです。

まずは自分で作成したクラスをオープンクラスでメソッドを追加・してみたいと思います。

自分で作成したクラスをオープンクラスしてみる
class Hoge
  def self.huga(a)
    p "huga#{a}"
  end

  def self.piyo(b)
    p "piyo#{b}"
  end
end

# メソッドの確認
Hoge.methods(false)
# => [:huga, :piyo]

# オープンクラスでvarを追加する
class Hoge
  def self.var(b)
    p "var#{b}"
  end
end

Hoge.methods(false)
# varが追加されている
# => [:huga, :piyo, :var]

オープンクラスを使ってHogeクラスにvarメソッドを追加しました。次にモンキーパッチをしてみたいと思います。

自分で作成したクラスにモンキーパッチをしていみる
class Hoge
  def self.huga(a)
    p "huga#{a}"
  end

  def self.piyo(b)
    p "piyo#{b}"
  end
end

Hoge.huga("huga")
# => hugahuga

# モンキーパッチでhugaメソッドの振る舞いを変える
class Hoge
  def self.huga(a)
    p "hugahuga#{a}"
  end
end
# => hugahugahuga

上記のようにメソッドの振る舞いを後から変更することが出来ます。

同様にRubyの標準クラスも振る舞いを変更することが出来ます。

Stringクラスの振る舞いを変える
# オープンクラス
class String
  def add_method
    self + "_add_method"
  end
end

"Hoge".add_method
# => Hoge_add_method

# モンキーパッチ
"Hoge".upcase
# => HOGE

class String 
  def upcase
    p "monkey_patch"
  end
end

"Hoge".upcase
# => monkey_patch 

上記のように標準クラスも書き換える事が出来ます。使う際は注意して使用しましょう。

特異メソッドを使ってあとからメソッドを追加する

特異メソッドを使うことでインスタンスにメソッド追加することが出来ます。

特異メソッド
class Hoge
  def huga(a)
    p "huga#{a}"
  end

  def piyo(b)
    p "piyo#{b}"
  end
end


hoge = Hoge.new
hoge.var("var")
# => NoMethodError: undefined method `var' for #<Hoge:0x007f84aeb506f0>

# 特異メソッドvarをhogeインスタンスに追加
def hoge.var(c)
  p "var#{c}"
end


# hogeインスタンスだけvarメソッドを持っている
hoge.var("var")
# => varvar

hoge2 = Hoge.new
hoge2.var("var")
# => NoMethodError: undefined method `var' for #<Hoge:0x007f84aeb506f0>

上記のようにインスタンス固有のメソッドを持たせることが出来ます。

instance_evalでインスタンス変数を書き換える

instance_evalでインスタンス変数を書き変えることがが出来ます。
ただ、オブジェクト指向の基本概念である「カプセル化」が崩壊するので使うときは注意してください。

instance_eval
# クラスの作成
class Hoge
  def initialize
    @hoge = "hoge" 
  end
  def get_hoge
    @hoge
  end
end

hoge = Hoge.new
hoge.get_hoge
# => hoge

# instance_evalを使ってインスタンス変数の書き換え
hoge.instance_eval do
  @hoge = "hogehoge"
end

hoge.get_hoge
# => hogehoge

上記のようにインスタンス変数を書き換える事が出来ます。

おわりに

今回幾つか紹介しましたが、メタプログラミング手法はまだまだ沢山あります!
メタプログラミングは黒魔術的な事ができるのかなり面白いです。
ただ、実務で使う場合は、しっかり設計しないと思わぬバグを生みかねないので注意しましょう。