はじめに
メタプログラミング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を使うと以下のように記述できます
# クラスの作成
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
# クラスの作成
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の標準クラスも振る舞いを変更することが出来ます。
# オープンクラス
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でインスタンス変数を書き変えることがが出来ます。
ただ、オブジェクト指向の基本概念である「カプセル化」が崩壊するので使うときは注意してください。
# クラスの作成
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
上記のようにインスタンス変数を書き換える事が出来ます。
おわりに
今回幾つか紹介しましたが、メタプログラミング手法はまだまだ沢山あります!
メタプログラミングは黒魔術的な事ができるのかなり面白いです。
ただ、実務で使う場合は、しっかり設計しないと思わぬバグを生みかねないので注意しましょう。