Posted at

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

More than 3 years have 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


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


おわりに

今回幾つか紹介しましたが、メタプログラミング手法はまだまだ沢山あります!

メタプログラミングは黒魔術的な事ができるのかなり面白いです。

ただ、実務で使う場合は、しっかり設計しないと思わぬバグを生みかねないので注意しましょう。