読者です 読者をやめる 読者になる 読者になる

My Favorite Things - Coding or die.

とある技術者の経験記録、的な。

メタプログラミングRuby第二版::一章 - 頭文字M

メタプログラミングRubyを読んで、理解のために自分なりにまとめていく。

基本的にテストコードをRSpecで記述していく。

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

コードを記述するコードを書くこと。

Rubyは実行時にクラスやメソッド、インスタンス変数など、様々な情報を読み書きできるのでメタプログラミングに適している。

C/C++などの言語では、実行時には変数やメソッドなどの実体は消えてなくなってしまうので、プログラム実行中にそれらの情報にアクセスして何かすることは出来ない。

Javaについては、リフレクションを利用してクラスやメソッドの情報にアクセスすることが出来るが、Rubyほどに変更を加えることは出来ない。

Rubyコンパイル時・実行時などの明確な区別がなく、あらゆる言語要素が常にそこにあり、操作できるといえる。(これはLispと同じ思想といえる)

ちなみにObjective-Cも(Rubyほどではないかもしれないが)動的にメソッドを追加したり、差し替えたり(Method Swizzling)、解決できなかったメソッド呼び出しを自分で解決する手段を用意しており、ここまで上げた言語のなかではメタプログラミングが得意と言える。

魔術

Rubyにおいてメタプログラミングを使った技法は「魔術」と言われる。

魔術には良いものと悪いもの、すなわち「白魔術」と「黒魔術」が存在する。

Rubyは全面的にプログラマを信用し、言語側で安全のためにブロックしたりするという考えは持っていない。プログラマはミスを犯す”という視点に立って、安全性を重視しているJavaとは対照的と言える。

イントロスペクション

実行時に言語要素にアクセスすること。 JavaではリフレクションAPIを利用してアクセスする。

プロダクションコード:

class Greeting
  def initialize(text)
    @text = text
  end
  def welcome
    @text
  end
end

テストコード:

require 'spec_helper'
require 'introspection'

describe 'introspection' do
  let(:greeting) { Greeting.new('hello') }
  it 'クラス名が取得できること' do
    expect(greeting.class.to_s).to eq 'Greeting'
  end
  it 'インスタンスメソッドの一覧が取得できること' do
    # 引数の`false`は継承したメソッドは対象にしないという意味
    expect(greeting.class.instance_methods(false)).to eq [:welcome]
  end
  it 'インスタンス変数の一覧が取得できること' do
    expect(greeting.instance_variables).to eq [:@text]
  end
end

テストコードにあるように、以下の情報がgreetingインスタンスから取得できている。

ActiveRecordの例

ActiveRecordRuby on Rails で使用されているORMapperである。

以下はmoviesテーブルのレコードに対応するEntityクラスを定義した例である。

class Movie < ActiveRecord::Base
end

このように特定のクラスを継承するだけで実現できる。

テーブル名はクラス名Movieから取得し、カラムに対応するアクセサメソッドはDBのスキーマから取得し、実行時に動的にクラスに対してメソッドを追加することで実現できている。

実行時に情報が消えてしまうC/C++のような言語ではこのようなことは出来ず、ボイラープレートが増えてしまう。もちろんコードジェネレータを使用した「静的メタプログラミング」も可能だが、それは言語の外で行われるもので、プログラム実行時に行える「動的メタプログラミング」ほどスマートではない。(ジェネレータを実行する手間もあるだろうし、コード上からボイラープレートコードは消えない)

Rubyにおけるメタプログラミングは特殊ではない

Rubyにおいてメタプログラミングは特殊なものではなく、「通常」のプログラミングと明確に区別できるものではない。

そういった意味で、 Rubyを使いこなすにはメタプログラミングを理解することが重要である。