こんにちは、PDDのドゥックです。仕事の上でsend
メソッドをよく使って本当に便利だなと思って、そのようなメソッドをもっと知りたいので、調べてみたところメタプログラミングというものを見つけました。メタプログランミングは広くて難しい領域ですが、Rubyではそれが簡単にできるように、いくつかのメソッドが用意されました。この記事ではそれらのメソッドを紹介したいと思っています。
1. メタプログラミングとは?
簡単にというと、メタプログラミングはコードを書くコードを書くことです。コードでオブジェクト(クラス、 モジュール。。)の中身を変更したり、実行時に新しいメソッドを追加したりできます。
2. 用意されたメソッド
2.1 eval
evalメソッドに渡す文字列をRubyプログラムとして評価してその結果を返します。
例:メソッドを定義する
eval('def hoge; p "Hello World" end') hoge #=> "Hello World"
2.2 define_method
渡す文字列をメソッド名としてインスタンスメソッドを定義します。
例:
{hoga: 1, hoge: 2}.each do |key, value| define_method key do p value end end hoga hoge #=> 1 #=> 2
2.3 send
オブジェクトのメソッド名を引数にして呼び出し、メソッドの実行結果を返します。
例:
class Hoge def fuga p 'fuga' end def fuge p 'fuge' end end hoge = Hoge.new %w(ga ge).each do |key| hoge.send("fu#{key}") end #=> "fuga" #=> "fuge"
2.4 method_missing
呼びだされたメソッドが定義されていなかった時、Rubyインタプリタがこのメソッド を呼び出します。デフォルトではこのメソッドは例外NoMethodError
を発生させます。method_missing
メソッドを再定義した場合、メソッドが見つからない時に欲しい処理を追加することができます。
例:
class Hoge attr_accessor :status def method_missing(method_name, *arguments) if method_name[-1] == '?' status == method_name[0..-2] else super end end end hoge = Hoge.new hoge.status = 'open' p hoge.open? #=> true
2.5 instance_variable_get / instance_variable_set
instance_variable_set:オブジェクトのインスタンス変数に値を設定します。インスタンス変数が定義されていなければ新たに定義されます。
instance_variable_get:オブジェクトのインスタンス変数の値を取得して返します。インスタンス変数が定義されていなければ nil を返します。
例:@hoga, @hogeというインスタンス変数を定義する。
{ga: 1, ge: 2}.each do |key, value| instance_variable_set("@ho#{key}", value) end p @hoga p @hoge #=> 1 #=> 2 {ga: 1, ge: 2}.each do |key, value| p "ho#{key}: #{instance_variable_get("@ho#{key}")}" end #=> "hoga: 1" #=> "hoge: 2"
3. 注意
メタプログラミングは魔術のように色々動的な処理に書き換える事が出来ますが、コードの可読性の低下、致命的なバグを生み出す可能性があります。使う時には注意してください。
例えば、2.5では簡単に沢山のインスタンス変数を同時に定義できますが、コードが読み辛くなります。また、普段もし変数がnil
なら、ターミナルで変数名が表示されてその名前からエラーのところを見つけることができますね。ですが、こういう定義方法では名前で検索するのが難しいです。大きなプロジェクトなら結構面倒だと思います。
4. 終わりに
上記にいくつかのメソッドを紹介しましたが、メタプログラミング手法はまだまだ沢山あります。今まで、send
だけを使っていましたが、できれば他のメソッドも実際に使用したいと思っています。皆さんもぜひ使ってみてください!