Tuesday, December 11, 2007
rubyにおける強制型変換(coerce)
rubyにおける型変換
rubyではduck typingによる型を意識しないプログラミング手法が主流ですが、それでもどうしても型を意識する必要がある場面は存在します。そういった時に頻繁に用いられる型変換は、意識的に用いられるto_s,to_a,to_hといった緩い型変換と、自然に用いられるto_str,to_int,to_hashといった厳密な型変換なのですが、第三の型変換機能として、coerceと呼ばれる強制型変換があります。
coerceとは
coerceが必要となるのは他の型のオブジェクトをレシーバとして演算子形式のメソッドを呼び出し、演算操作を行う場合です。このようなコードを考えて下さい
class HogeNumber attr_accessor :value def initialize(v) @value = v end def *(other) @value * other end end x = HogeNumber.new(5) puts x * 6 #30 puts 6 * x #Fixnumの*メソッドの引数にHogeNumber型のxを引数として渡す #TypeError: HogeNumber can't be coerced into Fixnum
xの*メソッドを実装することでx * 6は機能しますが、6 * xで呼び出されるのはFixnumの*であるため、このままでは機能しません。しかし、rubyには勿論オーバーロードなどは存在しないため、Fixnumの*をオーバーロードで定義することは出来ませんし、たとえ出来たとしても、FixnumでなくFloatの場合、Complexの場合…などと考えていくと気が遠くなりますね。そこで、rubyでは以下のような方法でそれを解決します。
演算子形式のメソッドは、引数とレシーバの型が異なる場合に、型変換を行うメソッドが引数のクラスに実装されているものとして、具体的な型変換を引数のメソッドに委譲します。このとき、引数として自身(selfなど)を渡します。つまり、ダブルディスパッチですね。
このときダブルディスパッチで呼び出されるメソッドこそがcoerceであり、つまりは、6 * xを行った際の型変換は、xのクラス(HogeNumber)側のcoerceメソッドによって行われます。よって、任意のクラスを他の型へcoerceするためには、coerceメソッドを実装する必要があります。
実装していなければ、先のコードを例にすれば、 6 * xを行っている行で
TypeError: HogeNumber can't be coerced into Fixnum
といったエラーを吐きます。
coerceを実装する
ではcoerceは具体的に何を行うメソッドなのでしょうか?
一般的に、coerceメソッドは、引数を一つ取り、レシーバと引数をより汎用的な同一の型に型変換した上で、[引数,レシーバ]を返すことが求められます。
引数として渡されるのは演算子形式のメソッドを呼び出した時のレシーバなので、この引数の型を頼りに具体的な型変換を行います。
class HogeNumber def coerce(other) if Integer === other then [other,@value] else [Float(other),Float(@value)] end end end p 6 * HogeNumber.new(5) #30 p 1.5 * HogeNumber.new(15) #22.5
coerceを実装する際に注意しなければならないのは、変換後の型を変換前より汎用的な型にすること、引数とレシーバの型を同一にすることです。演算子形式のメソッドでは、coerceによる型変換の後に演算子形式のメソッドによる演算を行う為、気をつけないと無限ループに陥ります。