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

Memento memo.

Today I Learned.

Effective Ruby 第五章 メタプログラミング まとめ その1

Effective Ruby 第四章 例外 まとめ - Memento memo. の続きです。

第五章 メタプログラミングについての前半部です。

Effective Ruby

Effective Ruby

モジュール、クラスフックを使いこなす

  • Rubyのイベント通知 -> フック関数の実行は、適切な名前のメソッドを書くだけで実行可能
  • 定義できるフックは10種類
hookメソッド タイミング
included moduleがincludeされる
extended moduleがextendされる
prepended moduleがprependされる
inherited classが継承される
method_added method追加
method_removed method削除
method_undefined method定義解除
singleton_method_added 特異method追加
singleton_method_removed 特異method削除
singleton_method_undefined 特異method定義解除
  • 全てのフックメソッドは特異メソッドとして定義する
  • method追加、削除、定義解除系のフックは引数としてメソッド名を受け取る。クラス名を知りたい場合はselfを使う。
  • 全てのフックメソッドは自動的にprivateになる
  • フックメソッドに関連する以下のメソッドはオーバーライドしてはならない。フックを使うこと。
    • extend_object
    • append_features
    • prepend_features
module PreventInheritance
  class InheritanceError < StandardError; end

  def inherited(child_klass)
    raise(InheritanceError, "#{child_klass} cannot inherit from #{self}")
  end
end

::Array.extend(PreventInheritance)

class ChildArray < ::Array; end
#=> ChildArray cannot inherit from Array (PreventInheritance::InheritanceError)

inherited フックを使うとこんな感じの使い方になります。

クラスフックからはsuperを呼び出す

  • hookは他のモジュールで定義されたhookの制御を完全に奪ってしまう場合がある。
  • クラスフックメソッド内では必ず super を呼ぶのが行儀が良い。

method_missingではなくdefine_methodを使う

  • method_missingを使ってはいけない理由
    • パフォーマンス面でコストがかかる
    • エラーメッセージが分かりにくくなる
    • respond_to? 等のイントロスペクションメソッドが使えなくなる。
  • method_missingで本来やりたいことの大半はproxy, decorator patternの実装だが、これらはdefine_methodで代替可能
  • proxyの実装
class HashProxy
  Hash.public_instance_methods(false).each do |name|
    define_method(name) do |*args, &block|
      @hash.send(name, *args, &block)
    end
  end

  def initialize
    @hash = {}
  end
end
  • decoratorの実装
require('Logger')

class AuditDecorator
  def initialize(object)
    @object = object
    @logger = Logger.new($stdout)

    @object.public_methods.each do |name|
      define_singleton_method(name) do |*args, &block|
        @logger.info("calling '#{name}' on #{@object.inspect}")
        @object.send(name, *args, &block)
      end
    end
  end
end

上記例の用に、オブジェクトの場合は define_singleton_method で特異メソッドを定義できます。

  • どうしても method_missing を避けられない場合は respond_to_missing? を併用する。
  • respond_to_missing?repond_to? がメソッドを見つけられなかった場合の最後のチャンスをプログラマに委ねる。

evalの多様な変種間の違いを把握する

  • eval, instance_eval, class_eval で直接文字列を評価するのは避ける。
  • 代わりに class_exec, instance_exec とブロックを使うと変数のvalidation等が行えるようになる。

参考

secret-garden.hatenablog.com


細かいところはメタプログラミングRubyを読む必要がありそうですね。

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

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

続きます。