Effective Ruby 第五章 メタプログラミング まとめ その2
Effective Ruby 第五章 メタプログラミング まとめ その1 - Memento memo. の続きです。
モンキーパッチの代わりとなるものを検討する
モンキーパッチとは
- モンキーパッチとは、実行時にコアクラスを拡張したり挙動を書き換える(パッチを当てる)こと。Active Supportが有名。
- 複数パッチが衝突すると大事故になるのでできるだけモンキーパッチは避けたい
方法1 module関数を使う
愚直にmoduleを定義&オブジェクトをextendする方法です。
module OnlySpace ONLY_SPACE_UNICODE_RE = %r/\A[[:space]]*\z/ def self.only_space? (str) if str.ascii_only? !str.bytes.any? do |b| b != 32 && !b.between?(9, 13) end else ONLY_SPACE_UNICODE_RE === str end end def only_space? OnlySpace.only_space?(self) end end str1 = " \r\n" puts OnlySpace.only_space?(str1) # OOPっぽくなくなる str2 = "hello" str2.extend(OnlySpace) puts str2.only_space? # extendしないと使えない
方法2 新しい別のクラスを作成する
Stringクラスの代わりにStringExtraクラスを作成します。 継承ではなく委譲を使ってStringライクなStringExtraを実装します。(実装省略)
方法3 Refinements機能を使う
Ruby 2.1から入ったRefinements機能を使います。
Refinementsでは、パッチの適用範囲がレキシカルスコープの中に限定されます。以下の例で String#loud
は Foo
の外で呼ぶことはできません。
使い方はこんな感じになります。
module Loud refine String do def loud "#{self}!!!" end end end class Foo using(Loud) def initialize(str) puts str.loud end end Foo.new("wei") #=> wei!!!
参考
エイリアスチェイニングで書き換えたメソッドを呼び出す
- 既存のメソッドに新しい名前を与え、元のメソッド名でメソッドを再定義して最終的に元のメソッドを呼び出す。
- エイリアス作成時にメソッド名がユニークになるよう注意
- エイリアスチェイニングを取り消すメソッドも作成する
alias_methodについて
alias_method(new_name, original_name)
でmethodにエイリアスを貼ることができます。
参考: ref.xaio.jp
sample
module LogMethod def log_method(method) orig = "#{method}_without_logging".to_sym if instance_methods.include?(orig) raise(NameError, "#{orig} isn't a unique name") end alias_method(orig, method) define_method(method) do |*args, &block| $stdout.puts("calling method '#{method}'") result = send(orig, *args, &block) $stdout.puts("'#{method}' returned #{result.inspect}") result end end #エイリアスチェイニングを取り消すメソッドも作成する def unlog_method(method) orig = "#{method}_without_logging".to_sym if !instance_methods.include?(orig) raise(NameError, "was #{orig} already removed?") end remove_method(method) alias_method(method, orig) remove_method(orig) end end Array.extend(LogMethod) #=> Array Array.log_method(:first) #=> :first [1,2,3].first # calling method 'first' '# first' returned 1 #=> 1 Array.unlog_method(:first) #=> Array irb(main):008:0> [1,2,3].first #=> 1
Procの引数の個数の違いに対応できるようにする
- Procオブジェクト生成には"強いProc"と"弱いProc"がある。
lambda?
メソッドで識別可能。 - 弱い(Weak)Proc: 引数の扱いが緩く、間違った個数の引数を与えてもエラーにならない。e.g. block
- 強い(Strong)Proc: 通常メソッド呼び出しと同じで、引数の個数が違うとArgumentError例外が発生する。e.g. lambda
- Proc#arityメソッドを使うと、Procオブジェクトが期待する引数の数がわかる。
- Proc#arityメソッドで引数の個数の違いをうまく吸収させる。
モジュールのprependを使うときは慎重に考える
- 継承階層は
ancestors
メソッドで確認可能 - includeは継承階層においてレシーバの後にモジュールを挿入する。
- prependは継承階層においてレシーバの前にモジュールを挿入する。
- prependよりもalias_methodを使った方が柔軟になる
メタプログラミング面白いですね。Refinementsで局所的にクラス拡張するのは結構気に入りました。
まだ メタプログラミングRuby 第2版 も読めていないのですが、健全なメタプロパワーを高めていきたいです。
- 作者: Peter J.Jones
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/19
- メディア: Kindle版
- この商品を含むブログ (4件) を見る