Ruby でクラス定義の DSL 作成
cuzic です。
今日も Ruby の話題です。
理由はないのですが、大林さんの昔出した課題のような問題に取り組んでみました。
目標は、
A = AttrClass :accessor, :with_default => 1 do def initialize puts "initialized" end def do_something puts "do_something" end end a = A.new #=> intialized p a.accessor #=> nil a.accessor = "some value" p a.accessor #=> "some value" p a.with_default #=> 1 a.with_default = 2 p a.with_default #=> 2 a.do_something #=> do_something
となるような AttrClass メソッドを定義することです。
つまり、AttrClass メソッドの呼び出しが下記のコードと等価になるようにする、ということです。
class A attr_accessor :accessor attr_accessor :with_default def initialize @with_default = 1 puts "initialized" end def do_something puts "do_something" end end
これはやってみると、比較的簡単にできました。
次のようなメソッド定義になります。
def AttrClass *symbols, &block Class.new do |klass| symbols.each do |sym| case sym when Symbol attr_accessor sym when Hash klass.class_eval do sym.each do |key, value| attr_accessor key end end Module.new do new_original = klass.method :new define_method :new do |*args, &block| obj = new_original.call *args, &block sym.each do |key, value| obj.__send__ "#{key}=", value end obj end extend_object klass end end end klass.class_eval &block klass end end
ポイントは、Class#class_eval を使って、Class のコンテキストでブロックを評価していることや、new クラスメソツドを再定義して、インスタンス変数の初期化を行っているところでしょうか。
これを使えば、アクセサなどが多いクラス定義がより簡潔に書けます。