Rubyのメソッドに別名があってもいいじゃないか
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オブジェクトによる別名の方法を利用する必要がある。
blog comments powered by Disqus