Hatena::Groupunsigned

Dis Communication このページをアンテナに追加 RSSフィード

Tuesday, December 11, 2007

rubyにおける強制型変換(coerce)

rubyにおける強制型変換(coerce) - Dis Communication を含むブックマーク はてなブックマーク - rubyにおける強制型変換(coerce) - Dis Communication rubyにおける強制型変換(coerce) - Dis Communication のブックマークコメント

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による型変換の後に演算子形式のメソッドによる演算を行う為、気をつけないと無限ループに陥ります。

トラックバック - http://unsigned.g.hatena.ne.jp/Trapezoid/20071211