トップ 最新の日記 ユーザー登録 ログイン ヘルプ

のびのびなRuby日記 このページをアンテナに追加 RSSフィード

2007-06-16

メタプログラミング その3  メタプログラミング その3 - のびのびなRuby日記 を含むブックマーク はてなブックマーク -  メタプログラミング その3 - のびのびなRuby日記  メタプログラミング その3 - のびのびなRuby日記 のブックマークコメント

instance_evalを使うと、インスタンス毎にインスタンスメソッドを追加出来ますが、インスタンス毎にインスタンスメソッドを定義出来ることに、何のメリットがあるのかが分かりません。

class Employee
  #fooメソッドを呼ぶと、barメソッド(インスタンスメソッド)が定義される。
  def foo
    instance_eval %Q{
      def bar
        puts "You are calling bar method"
      end
    }
  end
end

実行例

irb(main):001:0> load 'employee.rb'
=> true

Employeeクラスのインスタンスを生成
irb(main):002:0> emp1 = Employee.new
=> #<Employee:0x701ac>

#emp1に対してfooメソッドを実行したので、emp1にはbarというインスタンスメソッドが追加で定義された。
irb(main):003:0> emp1.foo
=> nil

#emp1はbarメソッドが定義されているので、barメソッドを呼ぶとメッセージが表示される。
irb(main):004:0> emp1.bar
You are calling bar method
=> nil

#Employeeクラスのインスタンスを別途生成
irb(main):005:0> emp2 = Employee.new
=> #<Employee:0x60be4>

#emp2はfooメソッドを呼んでないので、barメソッドは定義されない。従ってemp2に対してbarメソッドを実行すると、そんなメソッドはないと怒られる。
irb(main):006:0> emp2.bar
NoMethodError: undefined method `bar' for #<Employee:0x60be4> from (irb):6

RubyKaigi2007  RubyKaigi2007 - のびのびなRuby日記 を含むブックマーク はてなブックマーク -  RubyKaigi2007 - のびのびなRuby日記  RubyKaigi2007 - のびのびなRuby日記 のブックマークコメント

丁度一週間前ですが今年もRubyKaigiへ参加してきました。Dave Thomasさんの話にはRubyに対する愛が満ちあふれていた。凄く良かったです。またDaveさんにAgile Web Development with Rails(以下AWDwRと略) と達人プログラマサインして頂きました。

特にAWDwRには昨年DHHにサインして頂き、そして今年はDaveさんにサインして頂いたので、著者二人のサインを二年越しで獲得出来たのは凄く嬉しいです。

RubyKaigiスタッフの皆様、お疲れさまでした。また来年楽しみにしております。

メタプログラミング その2  メタプログラミング その2 - のびのびなRuby日記 を含むブックマーク はてなブックマーク -  メタプログラミング その2 - のびのびなRuby日記  メタプログラミング その2 - のびのびなRuby日記 のブックマークコメント

今回の例も実用性ありませんが、生成したオブジェクトに対して動的にアクセサとインスタンスフィールドを追加出来るメソッドdefine_accessorを定義してみました。

class Song
  def initialize(name, artist, duration)
    @name = name
    @artist = artist
    @duration = duration
  end

  def define_accessor(*syms)
      syms.each do |sym|
        instance_eval %Q{
          def #{sym}
            return @#{sym}
          end
          def #{sym}=(val)
            @#{sym}=val
          end
        } 
      end
  end
end 

実行例

irb(main):001:0> load 'song.rb'
=> true

irb(main):002:0> s = Song.new("Snow Smile", "Bump Of Chiken", 360)
=> #<Song:0x67570 @duration=360, @name="Snow Smile", @artist="Bump Of Chiken">

#Songクラスのオブジェクトsにname,artist,durationというアクセサが定義されていないことを確認
irb(main):003:0> s.methods.each do |method|
irb(main):004:1*   if /^(name|artist|duration)$/ =~ method
irb(main):005:2>     puts "#{method} method found"
irb(main):006:2>   end
irb(main):007:1> end
=> ["instance_variables", "__id__", "to_s", "send", "dup", "private_methods", "display", "=~", "is_a?", "class", "tainted?", "singleton_methods", "eql?", "untaint", "instance_of?", "method", "id", "instance_variable_get", "inspect", "instance_eval", "extend", "nil?", "__send__", "frozen?", "taint", "object_id", "instance_variable_defined?", "public_methods", "hash", "to_a", "clone", "protected_methods", "respond_to?", "freeze", "kind_of?", "==", "instance_variable_set", "type", "===", "equal?", "methods", "define_accessor"]

#sに対して動的にname,artist,durationというアクセサを定義
irb(main):008:0> s.define_accessor(:name, :artist, :duration)
=> [:name, :artist, :duration]

#再度sにname,artist,durationというアクセサが定義されているかどうか確認。今度は3つのアクセサが定義されていることが分かります。
irb(main):009:0> s.methods.each do |method|
irb(main):010:1*   if /^(name|artist|duration)$/ =~ method
irb(main):011:2>     puts "#{method} method found"
irb(main):012:2>   end
irb(main):013:1> end
name method found
artist method found
duration method found
=> ["inspect", "clone", "method", "public_methods", "instance_variable_defined?", "equal?", "freeze", "methods", "respond_to?", "dup", "instance_variables", "__id__", "object_id", "eql?", "id", "singleton_methods", "send", "taint", "define_accessor", "frozen?", "instance_variable_get", "__send__", "instance_of?", "to_a", "name", "type", "protected_methods", "instance_eval", "name=", "artist", "==", "display", "===", "instance_variable_set", "artist=", "kind_of?", "extend", "to_s", "hash", "class", "duration", "tainted?", "=~", "private_methods", "nil?", "untaint", "duration=", "is_a?"]
irb(main):014:0> s.instance_variables
=> ["@duration", "@name", "@artist"]

#sに対して動的にgenreアクセサとインスタンスフィールドを追加
irb(main):015:0> s.define_accessor(:genre)
=> [:genre]

#sのインスタンスフィールドを確認するが、まだgenreフィールドに値を設定していないのでduration,name,artistというインスタスフィールドしか表示されない。
irb(main):016:0> s.instance_variables
=> ["@duration", "@name", "@artist"]

#sに新しく追加したgenreというインスタンスフィールドに値を設定
irb(main):017:0> s.genre = "J-Pop"
=> "J-Pop"

#再度sのインスタンスフィールドを確認。今度はgenreというインスタンスフィールドが追加されています。
irb(main):018:0> s.instance_variables
=> ["@genre", "@duration", "@name", "@artist"]


#sにname,artist,duration,genreというアクセサがあるかどうか確認してみます。4つ共アクセサとして存在していることが確認できました。
irb(main):019:0> s.methods.each do |method|
irb(main):020:1*   if /^(name|artist|duration|genre)$/ =~ method
irb(main):021:2>     puts "#{method} method found"
irb(main):022:2>   end
irb(main):023:1> end
name method found
artist method found
duration method found
genre method found
=> ["inspect", "genre=", "clone", "method", "public_methods", "instance_variable_defined?", "equal?", "freeze", "methods", "respond_to?", "dup", "instance_variables", "__id__", "object_id", "eql?", "id", "singleton_methods", "send", "taint", "define_accessor", "frozen?", "instance_variable_get", "__send__", "instance_of?", "to_a", "name", "type", "protected_methods", "instance_eval", "name=", "artist", "==", "display", "===", "instance_variable_set", "artist=", "kind_of?", "extend", "to_s", "hash", "class", "duration", "tainted?", "=~", "private_methods", "nil?", "untaint", "duration=", "genre", "is_a?"]

#先ほどとは異なるSongクラスのオブジェクトを生成
irb(main):024:0> s1 = Song.new("Whatever", "Oasis", 300)
=> #<Song:0x44444 @duration=300, @name="Whatever", @artist="Oasis">

#s1のインスタンスフィールドを確認すると、duration,name,artistはあるが、genreはないことが確認されます。       
irb(main):025:0> s1.instance_variables
=> ["@duration", "@name", "@artist"]

#s1にname,artist,duration,genreというアクセサが定義されているか確認。どのアクセサも定義されていないことが分かります。
irb(main):026:0> s1.methods.each do |method|
irb(main):027:1*   if /^(name|artist|duration|genre)$/ =~ method
irb(main):028:2>     puts "#{method} method found"
irb(main):029:2>   end
irb(main):030:1> end
=> ["instance_variables", "__id__", "to_s", "send", "dup", "private_methods", "display", "=~", "is_a?", "class", "tainted?", "singleton_methods", "eql?", "untaint", "instance_of?", "method", "id", "instance_variable_get", "inspect", "instance_eval", "extend", "nil?", "__send__", "frozen?", "taint", "object_id", "instance_variable_defined?", "public_methods", "hash", "to_a", "clone", "protected_methods", "respond_to?", "freeze", "kind_of?", "==", "instance_variable_set", "type", "===", "equal?", "methods", "define_accessor"]

メタプログラミング その1  メタプログラミング その1 - のびのびなRuby日記 を含むブックマーク はてなブックマーク -  メタプログラミング その1 - のびのびなRuby日記  メタプログラミング その1 - のびのびなRuby日記 のブックマークコメント

まつもと直伝 プログラミングのオキテ 第6回を参考にしてメタプログラミングの練習をしてみました。

全然実用性はないのですが、クラス内にsetter :nameやgetter :nameと書いておくと、setNameで@nameに値をセット出来て、getNameで@nameの値を取得出来るようになります。

#ファイル名:module.rb
class Module
  def getter(*syms)
    syms.each do |sym|
      class_eval %{
        def get#{sym.to_s.capitalize}
          return @#{sym}
        end
      }
    end
  end

  def setter(*syms)
    syms.each do |sym|
      class_eval %{
        def set#{sym.to_s.capitalize}=(val)
          @#{sym}=val
        end
      }
    end
  end
end

実行例

irb(main):001:0> load 'module.rb'
=> true
irb(main):002:0> class Song
irb(main):003:1>   setter :name, :artist, :duration
irb(main):004:1>   getter :name, :artist, :duration
irb(main):005:1> end
=> [:name, :artist, :duration]
irb(main):006:0> song = Song.new
=> #<Song:0x5bb1c>
irb(main):007:0> song.setName="Wherever You Will Go"
=> "Wherever You Will Go"
irb(main):008:0> song.setArtist="The Calling"
=> "The Calling"
irb(main):009:0> song.setDuration="204"
=> "204"
irb(main):010:0> puts "Name: #{song.getName} / Artist: #{song.getArtist} / Duration: #{song.getDuration}"
Name: Wherever You Will Go / Artist: The Calling / Duration: 204