メタプログラミングは Ruby の忍者だけが使うもので、普通の人間には使えないとよく聞きます。 しかし、実はメタプログラミングはまったく怖いものではありません。 このブログ記事は、この種の考え方に挑戦し、メタプログラミングを平均的な Ruby 開発者に近づけて、彼らもその恩恵を受けられるようにするためのものです。
メタプログラミングは多くのことを意味し、しばしば非常に誤用され、使用に関して極端になる可能性があることに留意すべきですので、誰もが日常のプログラミングで使用できるいくつかの実例を投げかけることにします。 つまり、実行時にメソッドやクラスを定義することができるのです。 クレイジーでしょう? 一言で言えば、メタプログラミングを使用すると、クラスを再オープンして修正したり、存在しないメソッドをキャッチしてその場で作成したり、繰り返しを避けて DRY なコードを作成したりできます。
基本
本格的なメタプログラミングに飛び込む前に、基本を探究する必要があります。 そして、それを行う最良の方法は、例によって行うことです。 まずは例題から始めて、Ruby のメタプログラミングを順を追って理解していきましょう。
class Developer def self.backend "I am backend developer" end def frontend "I am frontend developer" endend
2つのメソッドを持つクラスを定義しています。 このクラスの最初のメソッドはクラスメソッドで、2番目のメソッドはインスタンスメソッドです。 これはRubyの基本的なことですが、このコードの背後ではもっと多くのことが起こっており、先に進む前に理解しておく必要があります。 Developer
というクラス自体が、実はオブジェクトであることは指摘しておくべきでしょう。 Rubyでは、クラスも含めてすべてがオブジェクトです。 Developer
Class
クラスのインスタンスになります。 Here is how the Ruby object model looks like:
p Developer.class # Classp Class.superclass # Modulep Module.superclass # Objectp Object.superclass # BasicObject
One important thing to understand here is the meaning of self
. The frontend
method is a regular method that is available on instances of class Developer
, but why is backend
method a class method? Every piece of code executed in Ruby is executed against a particular self. When the Ruby interpreter executes any code it always keeps track of the value self
for any given line. self
is always referring to some object but that object can change based on the code executed. For example, inside a class definition, the self
refers to the class itself which is an instance of class Class
.
class Developer p self end# Developer
Inside instance methods, self
refers to an instance of the class.
class Developer def frontend self endend p Developer.new.frontend# #<Developer:0x2c8a148>
クラスメソッドの内部では、self
はある方法でクラス自身を参照します(この記事の後で詳しく説明します)
class Developer def self.backend self endendp Developer.backend# Developer
これはいいとして、結局クラスメソッドは何でしょう? その質問に答える前に、シングルトン・クラスや固有クラスとしても知られるメタクラスと呼ばれるものの存在に言及する必要があります。 先ほど定義したクラスメソッドfrontend
Developer
Developer
のインスタンスメソッドを定義することで、クラスメソッドとメタクラスは分離されます。
メタクラス
Ruby のすべてのオブジェクトはそれ自身のメタクラスを持っています。 それは開発者にはどういうわけか見えませんが、そこにあり、非常に簡単に使用することができます。 Developer
String
のオブジェクトを作成し、そのメタクラスを操作してみましょう:
example = "I'm a string object"def example.something self.upcaseendp example.something# I'M A STRING OBJECT
ここでやったことは、オブジェクトにシングルトンのメソッドを something
追加することでした。 クラスメソッドとシングルトンメソッドの違いは、クラスメソッドはクラスオブジェクトのすべてのインスタンスが利用できるのに対し、シングルトンメソッドはその単一のインスタンスにのみ利用できることです。
前の例は次のように書き直すことができます。
example = "I'm a string object"class << example def something self.upcase endend
構文は異なりますが、効果的に同じことを行っています。
Developer
クラスを作成した前の例に戻り、クラス メソッドを定義する他の構文をいくつか調べてみましょう。
def Developer.backend "I am backend developer"end
This is the same thing, we are defining the backend
class method for Developer
. We didn’t use self
but defining a method like this effectively makes it a class method.
class Developer class << self def backend "I am backend developer" end endend
Again, we are defining a class method, but using syntax similar to one we used to define a singleton method for a String
object. You may notice that we used self
here which refers to a Developer
object itself. First we opened Developer
class, making self equal to the Developer
class. Next, we do class << self
, making self equal to Developer
‘s metaclass. Then we define a method backend
on Developer
‘s metaclass.
class << Developer def backend "I am backend developer" endend
By defining a block like this, we are setting self
to Developer
‘s metaclass for the duration of the block. As a result, the backend
method is added to Developer
‘s metaclass, rather than the class itself.
Let’s see how this metaclass behaves in the inheritance tree:
As you saw in previous examples, there’s no real proof that metaclass even exists. But we can use a little hack that can show us the existence of this invisible class:
class Object def metaclass_example class << self self end endend
If we define an instance method in Object
class (yes, we can reopen any class anytime, that’s yet another beauty of metaprogramming), we will have a self
referring to the Object
object inside it. そして、class << self
Object
self
を返し、この時点ではメタクラスそのものです。 つまり、任意のオブジェクトに対してこのインスタンス・メソッドを呼び出すことで、そのオブジェクトのメタクラスを取得することができるのです。
そしてクレッシェンドとして、frontend
backend
がメタクラスのインスタンスメソッドであるという証明を見てみましょう。
ただし、メタクラスを取得するために実際に Object
を開き直し、このハックを追加する必要はありません。 Rubyが提供しているsingleton_class
metaclass_example
と同じですが、このハックを使うと実際にRubyがどのように動いているのか見ることができます。
p developer.class.singleton_class.instance_methods false#
“class_eval” と “instance_eval” を使ったメソッドの定義
クラスメソッドを作成する方法がもう一つあります。
このコード片は Ruby インタープリタがインスタンスのコンテキストで評価するもので、この場合は Developer
オブジェクトとなります。 オブジェクトにメソッドを定義する場合、クラスメソッドかシングルトンメソッドを作成することになります。 正確には、クラス メソッドはシングルトン メソッドですが、クラスのシングルトン メソッドであり、その他はオブジェクトのシングルトン メソッドです。
その一方で、class_eval
class_eval
がインスタンスメソッドを作成するためにどのように使用されるかということです。
まとめると、class_eval
self
instance_eval
self
が元のクラスのメタクラスを指すように変更するわけですね。
不足しているメソッドを即座に定義する
メタプログラミングのパズルのもう一つのピースは、method_missing
です。 オブジェクトのメソッドを呼び出すとき、Ruby はまずクラスに入って、そのインスタンスメソッドをブラウズします。 もしそこでメソッドが見つからなければ、祖先の連鎖をたどって検索を続けます。 それでも見つからなければ、method_missing
という名前の別のメソッドを呼び出します。
define_method
Module
define_method
def
define_method
define_method
method_missing
を組み合わせてDRYなコードを書くことができる以外は、あまり違いがありません。 正確には、クラス定義の際にスコープを操作するために、def
define_method
を使うことができますが、これは全く別の話です。
これは、define_method
を使用せずに、インスタンスメソッドを作成する方法を示しています。 しかし、これらでできることはもっとたくさんあります。
class Developer def coding_frontend p "writing frontend" end def coding_backend p "writing backend" endenddeveloper = Developer.newdeveloper.coding_frontend# "writing frontend"developer.coding_backend# "writing backend"
このコードは DRY ではありませんが、define_method
を使用して DRY にすることができます。
これはずっと良いですが、まだ完璧ではありません。 なぜでしょうか。 例えば新しいメソッド coding_debug
"debug"
method_missing
を使用すると、これを解決できます。
このコードの一部は少し複雑なので、分解して説明します。 存在しないメソッドを呼び出すと、method_missing
"coding_"
で始まるときだけ、新しいメソッドを作りたいのです。 そうでない場合は、super を呼び出して、実際に存在しないメソッドを報告する作業を行うだけです。 そして、その新しいメソッドを作成するために define_method
"coding_"
で始まる文字通り何千もの新しいメソッドを作ることができ、この事実がコードを DRY にしているのです。 define_method
Module
send
を使う必要があります。
まとめ
これは氷山の一角にすぎません。 Ruby のジェダイになるには、ここが出発点です。 メタプログラミングのこれらの構成要素をマスターし、その本質を本当に理解した後は、より複雑なもの、たとえば独自のドメイン特化言語 (DSL) を作成することに進むことができます。 DSLはそれ自体がトピックですが、これらの基本的なコンセプトは高度なトピックを理解するための前提条件となります。
この記事によって、メタプログラミングの理解に一歩近づき、もしかしたら独自のDSLを構築して、より効率的なコーディングに役立てることができればと願っています。