メタプログラミングRuby(動的ディスパッチと動的メソッド)

メタプログラミングRubyを読んだので、復習を兼ねて書きます。 問題点があればご指摘ください。

動的言語と静的言語

動的言語の例としては、 Ruby Python Perl など。

静的言語の例としては、 Java C Golang など。

違い1 動的言語は変数の定義時に型を宣言する必要がない。 静的言語では型の宣言は必須。

違い2 動的言語では、実行時に実行ファイルを評価するが、 静的言語では事前に実行するファイルをコンパイルする必要がある。

違い3 実行速度に関しては、ファイルを一行ずつ実行時に評価して実行する動的言語より、一挙にコンパイルしてから一挙に実行する静的言語の方が速い。

Rubyでは、動的言語であるがゆえに、method_missingメソッドを使ったゴーストメソッドの定義ができたり、実行時に動的にメソッドを呼び出したり、定義したりできる。

動的にメソッドを呼び出す

では、動的にメソッドを呼び出すとはどういうことなのか。 以下に実行例を書く。

# dynamic_call.rb
class DynamicCall
  def dynamic_method(choice)
    send(choice, 5)
  end

  def multiply_method(d_arg)
    d_arg * 2
  end

  def add_method(d_arg)
    d_arg + 100
  end
end

obj = DynamicCall.new
m_arg = "multiply_method"
a_arg = "add_method"

puts obj.dynamic_method(m_arg) # => 10
puts obj.dynamic_method(a_arg) # => 105

ここでわかることは、 掛け算の結果を得るためにmultiply_method、足し算の結果を得るためにadd_methodを直接的に定義したわけではなく、dynamic_methodメソッドを介して、それぞれの計算結果を得たということだ。 つまり、任意のメソッドを指定して、dynamic_methodにそのメソッドを呼び出して欲しいと指示したということ。

dynamic_methodメソッドの中にあるcallメソッドは、 第一引数のメソッドに第二引数の値を渡すという仕組みを提供してくれている。

したがって、以下のようにプログラムの実行者が実行中にメソッドを指定して呼び出すこともできる。

# dynamic_call.rb
class DynamicCall
  def dynamic_method(choice)
    send(choice, 5)
  end

  def multiply_method(d_arg)
    d_arg * 2
  end

  def add_method(d_arg)
    d_arg + 100
  end
end

obj = DynamicCall.new

print "呼び出したいメソッド名を教えてください "
arg = gets.chomp

puts obj.dynamic_method(arg)
# terminal

$ 呼び出したいメソッド名を教えてください multiply_method
$ 10

ターミナル上でmultiply_methodを入力すると、その計算結果である10が返ってくる。

このように、動的に、利用するメソッドを変更(動的にメソッドを呼び出す)ことを、動的ディスパッチと呼ぶ。

ディスパッチ(dispatch)は、英語で送り出すという意味がある。 動的にメソッドを送り出すということだ。

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

もう一つ、動的言語ゆえに、Rubyでは動的にメソッドを定義することができる。

# dynamic_method.rb
class Book
  def initialize(name, author)
    @name = name
  end

  def self.define_component(name)
    define_method(name) do
      @name + " is splendid!"
    end
  end
end


wilde = Book.new("the picture of dorian gray", "wilde")
shake = Book.new("hamlet")

Book.define_component :the_picture_of_drian_gray
Book.define_component :hamlet

puts wilde.the_picture_of_drian_gray # => the picture of dorian gray is splendid!
puts shake.hamlet # => hamlet is splendid! 

順に説明していくと、 1. まず、Bookクラスに必要となる要素を定義(initialize)。 2. self.define_component(インスタンスメソッドをクラスに作成するので、クラスメソッドとして定義)を定義し、その中で動的にメソッドを定義するdefine_methodメソッドを利用。 3. define_methodメソッドでは、受け取った引数からメソッド名を決定し、その中のロジックを継承した新しいメソッドを作成。 4. この時点で、Book.instance_methodsメソッドによって、引数として渡したメソッド名のメソッドが定義されていることを確認できる。 5. そして、作成したインスタンスを格納したオブジェクトに新しいメソッドを渡すことで、define_methodの中に組み込まれていたロジックを実行させることができるようになる。

ちょっと混乱しやすいかもしれないが、

Book.define_component :****** の******の部分に新しいメソッド名を入れてあげると、

def ******
  @name + "is splendid!"
end

というメソッドが作成されるということ。

これはインスタンスメソッドなので、Bookクラスで作成されたインスタンスは呼ぶことができる。

wilde = Book.new("the picture of dorian gray", "wilde")インスタンスを作成し、 wilde.****** で新しく作成したメソッドをオブジェクトに渡すと、メソッドの中身が帰ってくる。

=> the picture of dorian gray is splendid!

また、これにgetsを使うことで、ターミナルからメソッド定義ができるようになる。

# sample.rb
method_name = gets.chomp
Book.define_component method_name

book = Book.new("book", "author")
book.method_name
# => book is splendid!

動的言語について思ったこと

動的言語では、実行するまでエラーが出てこない。 それゆえに、実行までは何でも呼び出すことができる。 存在しないメソッドでさえも。

要するに、実行時には完全な状態にしておく必要があるということ。 動的でも静的でもそこのところは変わらない。 でも、動的言語は非常に柔軟な言語だと思った。