一覧へ戻る

更新:

カテゴリ: 技術
タグ:
  • 備忘録
  • 書籍
  • Ruby

メタプログラミングRuby 3章 ー動的なメソッドをやるぞー

『メタプログラミングRuby 第2版』の読書メモ。sendによる動的ディスパッチ、define_methodによる動的定義、そして「黒魔術」とも呼ばれるmethod_missing(ゴーストメソッド)の使い方を解説します。RailsのActiveRecordでも使われる技術の基礎を、サンプルコードと共にまとめました。

こんにちは!いとじゅんです!

前回に引き続き、メタプログラミングRuby 第2版の備忘録をまとめてみました。

メソッドの動的呼び出し

sendメソッドを使うことで実行時に呼び出すメソッドを動的に変更できる。sendメソッドの第一引数にシンボルまたは文字列を与えることで、そのメソッドを呼び出せる。

code
class CPK
    def hello
        p "かぐやっほー!!"
    end
end
 
kaguya = CPK.new
kaguya.send(:hello) # => "かぐやっほー!!"

このように実行時に呼び出すメソッドを決められることを、動的ディスパッチと呼ぶ。動的ディスパッチという用語は言語によって指すものが微妙に違う?おそらく、具体的な記法とか手法を指しているというより、「実行時に呼び出すメソッドを決められること」を指すふんわりとした用語っぽい。

リストにメソッド名を突っ込んで、動的ディスパッチを使ってメソッドを呼び出す。これをするとJITコンパイラとか辛そうだなー。

sendメソッドは以下のようにprivateを無視できる。怖くね?と思ったが、書籍によるとprivateメソッドを呼び出せるからこそ、sendを使っているらしい。

code
class CPK
  private
  def hello
    p "ヤオヨロー!!"
  end
end
 
yachi8000 = CPK.new
yachi8000.send(:hello) # => "ヤオヨロー!!"
yachi8000.hello # private method 'hello' called for an instance of CPK (NoMethodError)

動的メソッド定義

Module#define_methodを使ってインスタンスメソッドをその場で定義できる。

以下のコードのようにメソッド名とブロックを渡す。

code
class BlackonyX
  define_method :mikado do
  	"凸凸 凸凸 突天山"
  end
end
 
BlackonyX.new.mikado # => "凸凸 凸凸 突天山"

defキーワードによるメソッド定義と違って、define_methodのメソッド定義だとメソッド名を動的に決定できる。これがうれしい。

ちなみに、define_singleton_methodを使うとクラスメソッドを動的に定義できる。

ゴーストメソッドでネクロマンサーに

method_missingとは...継承チェーンが終端までたどり着いてもメソッドが見つからなかったときに発火するBasicObjectのメソッド。
NoMethodErrorを生み出しているやつらしい。

じゃあこれのなにが動的メソッドに関係しているのか。それは、method_missingをオーバーライドすることで、メソッドが見つからなかったときに処理を差し込めるんですね。

code
class CPK
  def method_missing(method)
    "#{method}、帰っちゃうの?"
  end
end
 
cpk = CPK.new
p cpk.send("かぐや") # => "かぐや、帰っちゃうの?"

このようにmethod_missingを使うことで、呼び出し側からはあたかも通常のメソッド呼び出しのように見せることをゴーストメソッドと呼ぶ。

ゴーストメソッドというネーミングが好きすぎる。ネクロマンサーじゃん。黒魔術っぽくなってきたな。

動的プロキシ

書籍の記述を引用する。

ゴーストメソッドを捕捉して、他のオブジェクトに転送するGhee::ResourceProxyなどのオブジェクトは、動的プロキシと呼ばれる。

うーん。ラッパークラスとは違うもの?よくわかっていない。また今度有識者に聞く。

メソッドが存在しているように見せるぜ

method_missingによって見かけ上の処理を書いた場合、メソッドは定義していないので以下のコードのように、respond_to?でfalseが返ってくる。

code
class CPK
  def method_missing(method)
    "#{method}、帰っちゃうの?"
  end
end
 
cpk = CPK.new
p cpk.send("かぐや") # => "かぐや、帰っちゃうの?"
p cpk.respond_to?("かぐや") # => false

かぐや帰っちゃった...それは置いておいて。

こういうときはrespond_to_missing?をオーバーライドして、ゴーストメソッドが存在したときにtrueを返してあげるとよい。

定数が存在しているように見せるぜ

const_missingをオーバーライドすることで、存在しない定数の参照をキャッチできる。そうすることで、存在しない定数を返すことも処理を挟み込むこともできる。

ブランクスレート

ゴーストメソッドのバグは特定しづらい。

本来エラーを出すメソッドをオーバーライドして挙動を変えてるんだからやばいことになりやすいだろうなぁ。

そんなときに、メソッドが競合しづらい最小限のメソッドしかない状態のクラス(ブランクストレート)を使うことでリスクを低減できる。

メソッドを殺す魔法

既存のクラスのメソッドを利用したい場合もあるだろう。そんなときに、メソッドを削除したり未定義にしたりすることで、必要なメソッドを残しつつ競合を防止できる。

undef_methodを使うことでメソッドを未定義にすることができる。remove_methodでメソッドを削除できる。使い分けを知りたいぜ。

動的メソッド vs ゴーストメソッド

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

私はネクロマンサーになれないかもしれません。

まぁ明らかにゴーストメソッドのほうが注意点が多く、バグを生み出しやすい黒魔術感あるので確かに。

大魔術を探して

この章で学んだ内容をふんだんに含んだ大魔術が書かれたリポジトリがある。それが以下である。

皆さんご存じなRailsさんですね。まだ少し読んだだけですが、ActiveRecordとActiveSupportにはメタプログラミングがとんでもなく含まれていて幸せになれる。

以下のファイルとかは理解しやすくこの章の内容がたくさん含まれていて楽しい。読んでみてほしい。

activerecord/lib/active_record/dynamic_matchers.rb

おわりに

思考を吐き出しているだけなのでとても読みにくいものになっており、大変ごめんなさい。

学んだ黒魔術がrailsというメタプログラミングの大魔術に通じているというのがこの章における最大の学びでした。動的なメソッドというthe メタプログラミングを学べたのも大変良かったです。

ではまた。