背景
いま、Rubyの復習がてら、プログラミング言語Rubyを読んでいます。その中でcoerce
メソッドの使い方として、「なるほど。そういうことしたいときに使えるのか」と思った例が出ていたので、忘れないようメモしておきます。
動作環境
OS: MacOS 10.11.6
Ruby: 2.2.6
基本動作の確認
まず、coerceの基本動作の確認。以下のように、1 + 2/3
を計算するコードを書く。
require 'rational'
x = 1 + Rational(2,3)
puts x # => 5/3
上記で、1 + Rational(2,3)
が評価される際に、Ratinalのcoerceメソッドが以下のように、(あるいは以下と同等の式によって)呼ばれている。
Rational(2,3).coerce(1) # => [(1/1), (2/3)]
この内部の動きの説明として、以下、同著 3.8.7.4 算術演算子の強制型変換 から引用
coerceメソッドは、算術演算子から呼び出される。たとえば、Fixnumが定義している+演算子には、Rationalについての知識がないので、右辺がRatinalなら、どうすればそれを加算できるのかがわからない。corceは、この問題を解決する。数値演算子は、右辺の型の知識がなければ、その右辺のcoerceメソッドを呼び出し、引数として左辺を渡す。FixnumとRationalを加算するサンプルに戻ると、Ratinalのcoerceは、2個のRatinal値の配列を返す。そこで、Fixnumが定義する+演算子は。配列内の値の+演算子を呼び出せばよい。
上記のコード例だと、Rational(2,3).coerce(1)
が、Ratinal値2つの配列[(1/1), (2/3)]
を返してくれるので、1 + Rational(2,3)
は、Rational(1,1) + Rational(2,3)
を評価すればよいことになって、Ratinal値同士の加算に帰着する。
応用例
次に、本題の「なるほど。そういうことしたいときに使えるのか」の例です。
以下のような、XY座標上の点XYPointクラスを作成します。このクラスには、引数にスカラー値を期待する、*
メソッドによるスカラー倍も定義します。
class XYPoint
attr_reader :x, :y
def initialize x=0, y=0
@x, @y = x, y
end
def to_s
"(#@x,#@y)"
end
def *(a)
XYPoint.new(a * @x, a * @y)
end
end
p = XYPoint.new(2,3)
puts p * 10 # => (20,30)
ここで、XYPointをベクトルと見るとスカラー倍なら、倍する数10を *
の左側にして、
10 * p
と書きたい。しかし、上記のコードで最後の行の、point
と10
をひっくり返して、
puts 10 * p
にすると、以下のような「XYPointはFixnumに変換できない」旨のTypeErrorが発生します。
in `*': XYPoint can't be coerced into Fixnum (TypeError)
そこで、10 * p
を評価するときは、(XYPointの*
を使う)p * 10
を評価するように、XYPointクラスに以下のcoerceメソッドを追加します。
class XYPoint
・・・・(同上)・・・
def coerce(a)
[self, a]
end
end
こうすると、めでたく以下のように、10 * p
の結果(20,30)
が表示されました。
puts 10 * p # => (20,30)
参考
リファレンスマニュアル
instance method Numeric#coerce
追記
変数名にpを使うと、Kernel#p と紛らわしいので変数名をpointni
という編集リクエスト(上記メッセージ文末のpointni
は pointに
の誤字かな?)を一部の方から頂き、その方のお顔を立てて差し上げる
意味あいで、ご要望に添って一度はpoint
に修正しておりましたが、
Rails ガイドのサンプルコードにも
という、変数名を p
とする例がございますし、またこの投稿のコード例でも
p p
というような、明らかに紛らわしいコードを書いているわけではないので、 変数名を p
に戻させて頂きました。
何卒ご了承くださいますよう。