うぃろぅ.log

140字で綴りきれない日々の徒然備忘録

【備忘録】 「メタプログラミングRuby」を読む

うぃろぅです。

少し間が空いてしまいましたが今日も本を読んでいきます。
暑さに負けるな。

今日の本

メタプログラミングRuby

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

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

Rubyの基本を理解したら読むといいらしい。
具体的には「ループ処理をeachで書けるなら読める」と書いてある。ほんとぉ??

構成

Rubyの各機能についてのことが1章ずつ、その後Railsについて記載されている。

1章 頭文字M

多分「イニシャルエム」と読むと思う。夏は豆腐が美味しい。

メタプログラミングとは何かについてのイントロ。

「コードを書くコード」をプログラミングするのがメタプログラミングとしている。

「実行時に定義されるメソッド」とか「実行時に定義されるクラス」とかそういったものをこれから学習していくよ、というような内容となっている。

2章 オブジェクトモデル

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

Rubyではクラスもメソッドもオブジェクトである。
また、すでに定義されているクラスにメソッドや変数を追加することもできる。自由度の鬼。

ということは、自分しか使わないと思って定義したクラス名が他のgemのクラス名と被るということが起き得る。
そのため、名前の衝突を回避するために

module MyModule
  class MyClass
    (何か処理)
  end
end

こんな感じでクラスをネームスペースにラップすると良い。

メソッド探索

メソッド探索は「右へ一歩、それから上へ」となっている。
インスタンスから自分のクラスに一歩動き、それから繼承ツリーを上へ上へと登っていくイメージ。

[1] pry(main)> module M1; end
=> nil
[2] pry(main)> module M2
[2] pry(main)*   include M1
[2] pry(main)* end
=> M2
[3] pry(main)> module M3
[3] pry(main)*   prepend M1
[3] pry(main)*   include M2
[3] pry(main)* end
=> M3
[4] pry(main)> M3.ancestors
=> [M1, M3, M2]

また、同じモジュールを2回以上挿入しても読み込むのは最初の1回だけ。かしこめ。

3章 メソッド

動的メソッド

Module#define_methodを使えば動的にメソッドが生成できる。

[1] pry(main)> class MyClass
[1] pry(main)*   define_method :my_method do |my_args|
[1] pry(main)*     my_args * 3
[1] pry(main)*   end
[1] pry(main)* end
=> :my_method
[2] pry(main)> obj = MyClass.new
=> #<MyClass:0x00007fe2feed0ba0>
[3] pry(main)> obj.my_method(2)
=> 6

上記の例だとmy_methodがメソッド名、my_argsが引数。
my_methodの箇所を変数に変えればメソッド名は自由自在。
メソッド呼び出しもsendを使えば文字列で呼べる。ちょっと自由度高すぎんよー。

ゴーストメソッド

さらに「この名前で定義してね」とお願いするだけではなく「知らないメソッド名がきたらこの処理をしてね」ということもできる。

method_missingをオーバーライドすることでメソッド名が存在しない場合の処理を記載できる。

これを使う場合、respond_to_missing?メソッドもオーバーライドしてゴーストメソッドがrespond_to?に正しい値を返すようにするのがルール。ただ暗黙のルールとなっているため動的メソッドで済む場合はそちらを使った方がバグが入ってきづらい。

4章 ブロック

ブロックはクロージャ

スコープは

  • クラス定義
  • モジュール定義
  • メソッド

という単位で切り替わる。

class Hogedef fugaという記述によってスコープが切り替わるため、ここをClass.newdefine_methodと書くことでスコープが切り替わらなくなる。
キーワードによってスコープが切り替わると覚えておくとわかりやすい。

スコープが切り替わるキーワードをスコープゲートと呼び、スコープゲートを越えたいときにブロックが使われる。
なのでブロックはクロージャである、と。

呼び出し可能オブジェクト

ブロックはオブジェクトではない。
ブロックをオブジェクトにしたものはProcである。

Proc.newにブロックを渡してProcオブジェクトを生成し、Proc#callを呼び出すことでブロックを評価する。

他には

  • lambda
  • proc
  • ->(矢印ラムダ)
    p ->(x) { x + 1 }のような書き方になる

がある。

この辺りのことに関しては以下の記事が非常に詳しい。

qiita.com

とても助かる。

呼び出し可能オブジェクトとは、「評価ができて、スコープを持ち運べるコード」のことである。
呼び出し可能オブジェクトになれるのは以下。

  • ブロック
  • Proc
  • lambda
  • メソッド

DSLについては付録で詳しく学習することとする。

5章 クラス定義

オブジェクト指向といえばクラス、クラスといえばオブジェクト指向というくらい切っては切れないクラス定義について。

Rubyの場合、クラス定義にはあらゆるコードを記述できる。

class Hoge
  puts "こんにちは"
end

これでも動く。

動的にクラスを宣言する

class Hogeのようにclassキーワードを使わないでクラスを宣言してみる。

[1] pry(main)> c = Class.new(Array) do
[1] pry(main)*   def my_method
[1] pry(main)*     "Hello!"
[1] pry(main)*   end
[1] pry(main)* end
=> #<Class:0x00007faea61c9e20>

Class.newでOK。引数は継承元。
ただしこれだと無名クラスであるため、他の変数で使い回せない。
ではどうすればいいかというと

[2] pry(main)> Hello = c
=> Hello

これでmy_methodを持ったHelloクラスということになる。便利。

[5] pry(main)> k = Hello.new
=> []
[6] pry(main)> k.my_method
=> "Hello!"

これでインスタンスも生成できると。

特異メソッド

特定のインスタンスにのみメソッドを追加することもできる。これが特異メソッド。

こう書くと何か特別なメソッドなのかと考えるかもしれないが、何の事は無い。
クラスメソッドはクラスの特異メソッドであるから使ったことはある。

def obj.a_singleton_method; end
def MyClass.another_class_method; end

見た目からして同じに見える。
これを利用したものがアクセサ。

class Hoge
  attr_accessor :my_attribute
end

少し違うけれどざっくりプロパティみたいな。gettersetterの代わりと考えると覚えやすい。
このようなメソッドをクラスマクロと呼ぶらしい。

特異クラス

メソッドときたらクラス。
特異メソッドを宣言したとして、そのメソッドはどこに存在するのか。それがこの特異クラスとなる。

1] pry(main)> obj = Object.new
=> #<Object:0x00007f8c3c6d1648>
[2] pry(main)> singleton_class = class << obj
[2] pry(main)*   self
[2] pry(main)* end
=> #<Class:#<Object:0x00007f8c3c6d1648>>
[3] pry(main)> singleton_class.class
=> Class
[4] pry(main)> "abc".singleton_class
=> #<Class:#<String:0x00007f8c3c899200>>

class <<Object#singleton_classで確認することができる。 インスタンスを一つしか持てないため、シングルトンクラスとも呼ばれる。
特異クラスはメソッド探索時に自分自身のクラスより先に探索される。オーバーライドした場合はそちらが先。

オブジェクトモデルの7つのルール

  • オブジェクトは1種類のみ: 通常のオブジェクトかモジュールになる
  • モジュールは1種類のみ: 通常のモジュールかクラス、特異クラスのいずれかになる
  • メソッドは1種類のみ: モジュールに住んでいる
  • 全てのオブジェクトは「本物のクラス」を持つ: 通常クラスか特異クラスのどちらか
  • (BasicObject以外の)全てのクラスは1つの祖先を持つ: スーパークラスかモジュール
  • オブジェクトの特異クラスのスーパークラスはオブジェクトのクラス、クラスの特異クラスのスーパークラスはクラスのスーパークラスの特異クラス
  • メソッド探索はまず「本物のクラス」に向かって「右に」進み、その後継承チェーンを辿って「上に」進む

これをベースに考えれば大抵のことはうまくいく。特異クラスのところはややこしいからあとでまた読む。

クラスメソッド構文3つ

# その1
def MyClass.a_class_method; end

# その2
class MyClass
  def self.another_class_method; end
end

# その3
class MyClass
  class << self
    def yet_another_class_method; end
  end
end

どれも見たことがあったがその3の書き方にようやく合点がいった。なるほどこういう仕組みだったのね。

クラスメソッドを追加することをクラス拡張、オブジェクトに特異メソッドを追加することをオブジェクト拡張と呼ぶ。
先程までのように特異クラスをオープンしてメソッドを追加することももちろんできるが、使う機会が多いためObject#extendという拡張用のメソッドが用意されている。そういうことだったのか…。

クラス定義に関しては何回か読まないと難しそう。

6章 コードを記述するコード

コードは単なるテキストである。テキストであれば出力できる。だからコードを出力するコードが書けるよね、という論理。

Kernel#evalを使うと、コードの文字列を評価できる。

[1] pry(main)> arr = [10, 20]
=> [10, 20]
[2] pry(main)> element = 30
=> 30
[3] pry(main)> eval("arr << element")
=> [10, 20, 30]
[4] pry(main)> arr
=> [10, 20, 30]

もはやこうなってくるとなんでもあり感ある。

evalで実行するコードに別のスコープの変数を持ち込みたい場合、Bindingオブジェクトをevalに渡すことで実現できる。

[5] pry(main)> class MyClass
[5] pry(main)*   def my_method
[5] pry(main)*     @x = 1
[5] pry(main)*     binding
[5] pry(main)*   end
[5] pry(main)* end
=> :my_method
[6] pry(main)> b = MyClass.new.my_method
=> #<Binding:0x00007fd47001d118>
[7] pry(main)> eval "@x", b
=> 1

しかし何でもかんでも使えばいいということではない。実行時にはコードとして評価されるが、書いている / 読んでいる際はただの文字列なのである。そのため、シンタックスハイライトや補完が使えず、バグに気付きにくい。
さらに大きな問題として、悪意あるユーザーが悪意あるコードを実行できる危険性がある。

evalを使わないようにするのであれば、define_methodsendを使うことになる。適切なメソッド選択が大事。

社内でしか使わないからevalで書いた、という機能もリリースする可能性がある。そのため、最終的にはevalを使わないようにリファクタリングできる場合はすることも考えるべきである。

フックメソッド

オブジェクトに対して発生するイベントをキャッチすることができる。
イベント、というのは例えばクラスが継承されたりメソッドが定義されたりといったこと。

[1] pry(main)> class String
[1] pry(main)*   def self.inherited(subclass)
[1] pry(main)*     puts "#{self}#{subclass}に継承された"
[1] pry(main)*   end
[1] pry(main)* end
=> :inherited
[2] pry(main)> class MyClass < String; end
StringはMyClassに継承された
=> nil

こんな感じ。このように何かのイベントに対応して呼び出されるメソッドをフックメソッドと呼ぶ。

7章 エピローグ

メタプログラミングに必要な知識としては以上となる。
これ以降はRailsのコードを見ながら実際に使われている箇所を見ていく流れ。

これ以降は実際のコードについて見ていく箇所になるため、ここでのアウトプットに関しては以上とする。

まとめ

メタプログラミング、なんてかっこいい名前をつけられているが、Rubyの世界においてはそれもただのプログラミングに過ぎないよ、ということが丁寧に順を追って説明されている良書。

とりあえず知識のインデックスは作った。これからは実際に手を動かしてみる予定。

8章以降でRailsについて見たので次はRailsの本でも読もうかしら。

ではまた。