UUUM攻殻機動隊(エンジニアブログ)

UUUMのエンジニアによる技術ブログです

「メタプログラミングRuby」オススメです

こんにちは、タナカです。
夏は花火を見ながら飲むビールが最高ですね。

さて、本日はRubyをある程度使いこなせるようになった方へおすすめの「メタプログラミングRuby」を紹介したいと思います。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

メタプログラミングとは?

そもそもメタプログラミングとは何でしょうか、本書籍には以下一文で説明されています。

メタプログラミングとは、コードを記述するコードを記述することである。

Javaのソースコード上で使用できるアノテーションや、C++のテンプレートのようなコードを思い浮かべるとわかりやすいかもしれません。
これらのように、プログラムの一部を自動生成するものをメタプログラミングと呼びます。

ただし、これらの場合はコードジェネレータやコンパイラを使ったものなので、「静的メタプログラミング」と呼んで本書籍では区別しています。
Rubyの場合は言語自体がメタプログラミングをサポートしていて自由度の高い事ができるため、「動的メタプログラミング」と呼んでいます。

Rubyのメタプログラミング

まずは、ソースコードを見ていただくのが早いと思います。

class Movie < ActiveRecord::Base
end

movie = Movie.create
# moviesテーブルにtitleカラムが定義されている場合
movie.title = “博士の異常な愛情”
movie.title #=> “博士の異常な愛情”

このコード上ではMovieクラスにtitleメソッドが定義されていないにも係わらずエラーにならず設定、参照できています。

エラーにならず動作するのは、Movieクラスで定義されていないメソッドが呼ばれた際に、method_missing()というメソッドが呼ばれるのですが、このメソッドをActiveRecord::Base上で定義してあるからです。
ActiveRecord::Baseでは、テーブルのカラム名に一致するメソッドが実行されたときにmethod_missing()を通して値が設定されるようになっています。

Rubyでは、メタプログラミングの技術でプログラムを動的に拡張することができます。
ActiveRecordをはじめ様々なgem等で、このような技術が利用されています。

書籍の構成

本書籍は2部+付録の構成になっていて、1部は以下の章で構成されています。

  • 1章 頭文字M
  • 2章 オブジェクトモデル
  • 3章 メソッド
  • 4章 ブロック
  • 5章 クラス定義
  • 6章 コードを記述するコード

この中から第2章のオブジェクトモデルを少し紹介したいと思います。

オブジェクトモデル

この章では、クラスやオブジェクトを動的に拡張する方法から、メソッド探索等Rubyの内部処理について学ぶことができます。

オープンクラス

オープンクラスとは、クラスをオープンしてその場で拡張できる技法のことです。
たとえば、Stringクラスを拡張するようなこともでき、以下のようにして文字列にメソッドを追加できます。

class String
  def to_alphanumeric
    gsub(/[-\w\s]/, “”)
  end
end#3, the *magic, Number*?”. to_alphanumeric
#=> “3 the Magic Number”

オブジェクトモデルの内部

オブジェクトモデルのメソッドや、インスタンス変数は以下のようにしてプログラム中で取得することができます。
また、RubyではJavaとは違いインスタンス変数は値が代入されるまで存在しないという特徴があります。

class MyClass
  def my_method
    @v = 1
  end
end

obj = MyClass.new
obj.class # => MyClass

obj.my_method
obj.instance_variables # => [:@v]
obj.methods.grep(/my/) # => [:my_method]


オブジェクトからは、classを通して、クラス定義を調べることもできます。

f:id:tnuuu:20170729235035p:plain

クラスの真相

Rubyではクラスはオブジェクトであるという特徴があり、Class.newで新しいクラスを動的に生成することもできてしまいます。

“hello”.class # => String
String.class # => Class

Class.instance_mothods(false) # => [:allocate, :new, superclass]

オブジェクトとクラスは以下の関係があります。

f:id:tnuuu:20170730000851p:plain

オブジェクトobjのclassはMyClassクラスであり、MyClassクラスのclassはClassクラスになります。
まだ、MyClassクラスのsuperclassはObjectクラスで、さらに、ObjectクラスのsuperclassはルートクラスのBasicObjectクラスです。 (図にはありませんが)

この関係を覚えておくとRubyの理解が深まると思います。

メソッドを呼び出すときに何が起きているの?

メソッドを呼び出すとRubyはまず「メソッド探索」を行い、次に「メソッドの実行」を行います。

メソッド探索

メソッド探索では「レシーバー」と「継承チェーン」の2つの考えを把握しておく必要があります。

用語 説明
レシーバ 呼び出すメソッドが属するオブジェクト
継承チェーン クラスからスーパークラス、スパークラスのスーパークラス、ルートクラスのBasicObjectまで通るクラスの道筋
メソッド探索 レシーバーのクラスに入り、メソッドを見つけるまで継承チェーンを登ること

以下はメソッド探索のイメージ図です。

f:id:tnuuu:20170730012156p:plain

矢印の1でレシーバーのクラスに入り、矢印の2で継承チェーンを登りメソッドを見つけます。

本書籍では、ほかにモジュールを組み合わせたパターンや、多重インクルード時のメソッド探索についても解説されています。

なお、継承チェーンの内容はancestorsメソッドで確認することができます。

MySubclass.ancestors # => [MySubclass, MyClass, Object, Kernel, BasicObject]

メソッドの実行

メソッドを実行する際は、まずメソッドが呼び出されているオブジェクト(レシーバ)が何なのかを意識する必要があります。 以下の例は、レシーバであるobjがselfになること、レシーバを明示していないmy_methodメソッドのレシーバがobjになることを表したものです。

class MyClass
  def testing_self
    @var = 10 # selfのインスタンス変数
    my_method # self.my_methodと同じ
    self
  end

  def my_method
    @var = @var + 1
 end
end

obj = MyClass.new
obj.testing_self # => #<MyClass:0x007f93ab08a728 @var=11>

まとめ

今回紹介した内容は本書籍の内容のごく一部です。
次の3章では動的メソッドを利用して重複コードを排除する方法、2部ではRailsにおけるメタプログラミングについて解説されていて、いずれも興味深いものです。
Rubyについて深く理解するためにも、メタプログラミングRubyはぜひオススメです。