サーチ…


前書き

メタプログラミングは、2つの方法で記述することができます。

「他のプログラム(またはそれ自身)をデータとして書込みまたは操作するコンピュータプログラム、または実行時に実行されるコンパイル時に作業の一部を行うコンピュータプログラム」。

もっと簡単に言えば: メタプログラミングは、実行時にコードを書いて人生を楽にするコードを書くことです

インスタンス評価を使用した「with」の実装

多くの言語には、プログラマーがメソッド呼び出しの受信者を省略できるようにするwithステートメントがあります。

withは簡単にinstance_evalを使ってRubyでエミュレートできinstance_eval

def with(object, &block)
  object.instance_eval &block
end

withメソッドは、オブジェクト上のメソッドをシームレスに実行するために使用できます。

hash = Hash.new

with hash do
  store :key, :value
  has_key? :key       # => true
  values              # => [:value]
end

動的にメソッドを定義する

Rubyでは、実行時にプログラムの構造を変更することができます。これを行う1つの方法は、メソッドmethod_missingを使用してメソッドを動的に定義することmethod_missing

数字が777.is_greater_than_123?という構文で他の数字よりも大きいかどうかをテストできるようにしたいとします777.is_greater_than_123?

# open Numeric class
class Numeric
  # override `method_missing`
  def method_missing(method_name,*args)
    # test if the method_name matches the syntax we want
    if method_name.to_s.match /^is_greater_than_(\d+)\?$/
      # capture the number in the method_name
      the_other_number = $1.to_i
      # return whether the number is greater than the other number or not
      self > the_other_number
    else
      # if the method_name doesn't match what we want, let the previous definition of `method_missing` handle it
      super
    end
  end
end

method_missingを使用するときに覚えておくべき重要なことは、 method_missingも上書きする必要があるrespond_to?方法:

class Numeric
   def respond_to?(method_name, include_all = false) 
     method_name.to_s.match(/^is_greater_than_(\d+)\?$/) || super
   end
end

そうしないと、 600.is_greater_than_123を正常に呼び出すことができますが、 600.respond_to(:is_greater_than_123)はfalseを返します。

インスタンスでのメソッドの定義

Rubyでは、既存のクラスのインスタンスにメソッドを追加できます。これにより、そのクラスの残りのインスタンスの動作を変更することなく、クラスの動作とインスタンスを追加できます。

class Example
  def method1(foo)
    puts foo
  end
end

#defines method2 on object exp
exp = Example.new
exp.define_method(:method2) {puts "Method2"}

#with method parameters
exp.define_method(:method3) {|name| puts name}

send()メソッド

send()はメッセージをobjectに渡すために使用されobjectsend()は、 Objectクラスのインスタンスメソッドです。 send()の最初の引数は、オブジェクトに送信するメッセージ、つまりメソッドの名前です。それはstringまたはsymbolことができますが、 シンボルが優先されます。メソッドで渡す必要のある引数は、 send()残りの引数になりsend()

class Hello
  def hello(*args)
    puts 'Hello ' + args.join(' ')
  end
end
h = Hello.new
h.send :hello, 'gentle', 'readers'   #=> "Hello gentle readers"
# h.send(:hello, 'gentle', 'readers') #=> Here :hello is method and rest are the arguments to method.

より説明的な例がここにあります

class Account
  attr_accessor :name, :email, :notes, :address

  def assign_values(values)
    values.each_key do |k, v|
      # How send method would look a like
      # self.name = value[k]
      self.send("#{k}=", values[k])
    end
  end
end

user_info = {
  name: 'Matt',
  email: '[email protected]',
  address: '132 random st.',
  notes: "annoying customer"
}

account = Account.new
If attributes gets increase then we would messup the code
#--------- Bad way --------------
account.name = user_info[:name]
account.address = user_info[:address]
account.email = user_info[:email]
account.notes = user_info[:notes]

# --------- Meta Programing way --------------
account.assign_values(user_info) # With single line we can assign n number of attributes

puts account.inspect

注意: send()自体はもはや推奨されません。プライベートメソッドを呼び出す権限を持つ__send__()を使用するか、(推奨) public_send()



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow