Ruby の coerce に関するメモです。
coerce はあまり意識することは無いと思いますが、備忘録として記します。
coerce
例として、1K の値(1024)を求めるとします。
以下のようなことをすると、例外が発生します。
(メッセージには「... can't be coerced ...」と出ます。)
p 1 * :K #=> TypeError! (:K can't be coerced into Fixnum)
coerce できるよう、Symbol#coerce を定義してみます。
class Symbol
def coerce(other) # Symbol#coerce を定義する
equiv = {
K: 2 ** 10, # 2の10乗 1,024
M: 2 ** 20, # 2の20乗 1,048,576
G: 2 ** 30, # 2の30乗 1,073,741,824
}
if (other.kind_of? Numeric) && (val = equiv[self])
[other, val]
else
raise
end
end
end
p 1 * :K #=> 1024 (計算できるようになる)
p 1 * :M #=> 1048576 ( 〃 )
p 1 * :G #=> 1073741824 ( 〃 )
できるようになりました。
1 * :K
上の形は糖衣構文であり、実際はメソッド呼び出しです。
「1」がレシーバオブジェクト、「*」がメソッド、「:K」が引数です。
1.*(:K) # 実際は、こんな形
オブジェクト「1」は「*」というメソッドを知っていますが、そのメソッドで、「:K」(Symbol オブジェクト)を処理する方法を知りません。
その時、「1」は、自分を引数にして「:K」の coerce メソッドを呼び出します。
:K.coerce(self) # self は「1」
coerce メソッドの返り値として期待しているのは、自分が処理できるように変換された自分と相手の値の配列です。
[変換された自分の値, 変換された相手の値] # ここで自分が「1」、相手が「:K」
上の例では、Symbol#coerce を定義したので、値が返るようになっています。
(上の例では、[1, 1024] を返しています。Symbol#coerce 内では「:K」視点になるので相手(other)と自分(self)の位置が逆です)
自分と相手の変換された値を受け取った「1」は、以降は普通に「*」(積算)の処理を行って値を返すことができます。(結果は 1 * 1024 = 1024)
p 1 * :K #=> 1024
参考URI
「1」などNumeric オブジェクトの coerce の仕組みが書かれています。
ちなみに、Numeric#coerce は元からありますが、Symbol#coerce は無いようです。