Rubyのメソッドに別名があってもいいじゃないか

ブログを下記に移転しました。デザイン変更により移転先では記事が一層読みやすくなっていますので、よろしければ移動をお願い致します。

Rubyのメソッドに別名があってもいいじゃないか : melborne.github.com

                                                                                                            • -

Rubyのメソッドに別名を付ける方法を勉強したので
ここにまとめておきます

オブジェクトの別名

Rubyの変数はオブジェクトの参照にすぎないから
オブジェクトに別名を付けるのは簡単だ

a_friend = 'Charlie'
a_coworker = a_friend

database = DataBase.new
source = database

そう オブジェクトを参照している変数を
別の変数に代入すればいい


でもRubyではメソッドはオブジェクトではないから
Pythonのようにメソッドを変数に代入することはできない

def methodA
  :method_is_A
end

a_method = methodA # => :method_is_A


一見 代入が問題なく行われているように見えるけれども
変数a_methodに代入されているのは
methodAメソッドの実行結果に過ぎない

a_method.class # => Symbol

メソッドの別名

ではどうすればメソッドに別名を付けられるのか


いくつかの方法があって
その代表的な方法はaliasキーワードを使う方法だ

def base(name, rep)
  (["Great #{name}!"] * rep).join(", Yes, ")
end

alias :by_alias :base

parms = ['Charlie', 2]
base(*parms)      # => "Great Charlie!, Yes, Great Charlie!"

by_alias(*parms)  # => "Great Charlie!, Yes, Great Charlie!"

aliasには上のようにシンボルかメソッド名を新旧の順で渡す
引数の間にカンマがないのでこれがメソッドではなくて
キーワードだということが分かる


別名を付けるにはModule#alias_methodを利用する方法もある

module Base
  alias_method :by_alias_method, :base
end
include Base

parms = ['Charlie', 2]
by_alias_method(*parms) # => "Great Charlie!, Yes, Great Charlie!"


alias_methodは
Moduleクラスのプライベート・インスタンスメソッドなので
モジュールの文脈で呼ぶ必要がある
aliasと異なり引数に文字列を受け取ることができ
そこに式を置くこともできる

  alias_method :by_alias_method, (if today.sunny? then 'base' else 'base2' end)


またメソッド定義により別名を付ける方法がある

def by_method(*arg)
  base(*arg)
end

parms = ['Charlie', 2]
by_method(*parms)  # => "Great Charlie!, Yes, Great Charlie!"

受け取った引数をオリジナルのメソッドに委譲する形だ


またメソッドをオブジェクト化して別名を付ける方法もある

by_lambda = lambda { |*args| base(*args) }

by_method_obj = method(:base)

parms = ['Charlie', 2]
by_lambda[*parms]     # => "Great Charlie!, Yes, Great Charlie!"
by_method_obj[*parms] # => "Great Charlie!, Yes, Great Charlie!"

1つはKernel#lambdaを使って
メソッドをProcオブジェクトにして変数に代入する方法
1つはObject#methodを使って
メソッドをMethodオブジェクトにして変数に代入する方法だ


何れのオブジェクトもcallメソッドを呼ぶことで実行される
各callメソッドには別名としてそれぞれ
Proc#[] Method#[]が定義されていて
それを使うことで関数呼出しの構文的にその実行ができる


このように複数の別名定義方法があるけれど
注意しなければいけないことが1つある
それはベースのメソッドが再定義されたときの
挙動が異なるという点だ

def base(name, rep)
  (["Great #{name}!"] * rep).join(", Yes, ")
end

alias :by_alias :base

module Base
  alias_method :by_alias_method, (if true then 'base' else 'base2' end)
end
include Base

def by_method(*arg)
  base(*arg)
end

by_lambda = lambda { |*args| base(*args) }

by_method_obj = method(:base)

parms = ['Charlie', 2]
base(*parms)            # => "Great Charlie!, Yes, Great Charlie!"

by_alias(*parms)        # => "Great Charlie!, Yes, Great Charlie!"
by_alias_method(*parms) # => "Great Charlie!, Yes, Great Charlie!"
by_method(*parms)       # => "Great Charlie!, Yes, Great Charlie!"
by_lambda[*parms]       # => "Great Charlie!, Yes, Great Charlie!"
by_method_obj[*parms]   # => "Great Charlie!, Yes, Great Charlie!"

def base(name, rep)
  (["Poor #{name}!"] * rep).join(", No, ")
end

parms = ['Charlie', 2]
base(*parms)            # => "Poor Charlie!, No, Poor Charlie!"

by_alias(*parms)        # => "Great Charlie!, Yes, Great Charlie!"
by_alias_method(*parms) # => "Great Charlie!, Yes, Great Charlie!"
by_method(*parms)       # => "Poor Charlie!, No, Poor Charlie!"
by_lambda[*parms]       # => "Poor Charlie!, No, Poor Charlie!"
by_method_obj[*parms]   # => "Great Charlie!, Yes, Great Charlie!"


このコードの結果で分かるように
alias alias_method Methodオブジェクトを使った別名定義では
元のメソッドが実行される
つまりこれらの別名定義ではそのコンテキストを共有できない
従って元のメソッドが定義されたコンテキストを共有したい場合は
メソッド再定義かProcオブジェクトによる
別名の方法を利用する必要がある