reona.dev

メタプログラミングRuby 1-3章


今回はしばらく積読となっていたメタプログラミング Ruby を読んだので、内容についてまとめていきます。

想定読者

想定読者については以下のように書かれていました。

個人的には Ruby の開発プロジェクト経験がある方なら読めると思いました。

本書でできること

本のタイトル通りですが、Ruby を使ったメタプログラミング ができるようになります。 メタプログラミングとはなんぞや?という話ですが、それも本書で詳しく解説されています。 また、メタプログラミングをするために Ruby や Ruby on Rails の内部の動きを探っていくことで言語仕様の理解が深まります。

1 章 頭文字 M

冒頭で「 メタプログラミングとは、コードを記述するコードを記述することである。 」と書かれています。 また途中には「 メタプログラミングとは、言語要素を実行時に操作するコードを記述することである。 」と書かれています。

この定義の例として、Active Record が挙げられています。 例えば Ruby で Movie クラスがあるとして、Movie クラスのオブジェクト内部の変数を読み書きしたい場合はアクセサメソッドを用意する必要があります。 しかし、ActiveRecord::Base を継承することで、コード実行時にアクセサメソッドを定義してくれます。 これが先程挙げた、言語要素を実行時に操作するコードを記述するを表していますね。

Ruby はスクリプト言語であり、コンパイルをしなくてもいいので実行時に言語要素の殆どにアクセスが可能です。だから、コード実行時にメソッドを定義したり、もともとの定義を書き換えたりできます。メタプログラミングと非常に相性の良い言語が Ruby であり、メタプログラミングを習得することで Ruby の力をより引き出すことができます。

2 章 月曜日:オブジェクトモデル

この章からは主人公のボブとメンターのビルの対話形式で進行していきます。

module PrintExtension
  refine Kernel do
    def puts
      'puts!!!'
    end
  end
end

class User
  puts # => nil

  using PrintExtension

  puts # => "puts!!!"
end

puts # => nil

上は極端な例だが、Refinements を使えばメソッドのオーバーライドによる予期しないバグを防ぐことができるかもしれないですね。

class User
  include Animal
  include Person
end

user = User.new
user.print
pry(main)> User.ancestors
=> [User,
 Person,
 Animal,
 Object,
 PP::ObjectMixin,
 Kernel,
 BasicObject]

// PP::ObjectMixinはpryが勝手に用意した整形用の定数でirbでは表示されない

module を class に include することで、継承チェーンで言うと class の真上に module が挿入され、それより上のチェーンは押し上げます。 include でなく prepend を使えば、class の下に module が挿入されます。

Object クラスは Kernel モジュールを継承しているので、Kernel にメソッドを追加することでカーネルメソッドが全てのオブジェクトで使えるようになります。

オブジェクトのメソッドはクラス(モジュール)に住んでおり、継承チェーンの中で複数のクラスが同名のメソッドを持っていた場合は、先に呼ばれたクラスのメソッドが使わます。 user の print がどのクラスのメソッドなのか探索する際には、User クラスから上に向かって調べていくとよいでしょう。

pry(main)> Kernel.methods.grep(/print/)
=> [:sprintf,
 :printf,
 :print,
 :pretty_print,
 :pretty_print_cycle,
 :pretty_print_instance_variables,
 :pretty_print_inspect]

3 章 火曜日:メソッド

「可能であれば動的メソッドを使い、仕方がなければゴーストメソッドを使う」

と締めくくられていましたが、 #send もクラスやモデル、テーブル等の設計次第では使う場面はあまりない気がしました。(それでもメソッドを引数にとれるのは便利。。) メタプロ Ruby のサンプルコードはテーブル設計がひどく、そのようなプロジェクトにあたった際の対症療法としてはいいかもしれません。 #send を使いたいと思った時点で設計を見直してもいいかもしれないですね。 とはいえ Ruby は非常に柔軟で強力な言語だと再認識しました。

メタプロはとりあえず「難しそう」という先入観が強く後回しにしていましたが、読み始めるとひたすらに Ruby という言語を掘り下げていく内容で、夢中になって読み進めることができています。これ以降の章も別の記事でまとめたいと思います。