Ruby の case 文でクラスの所属関係をチェックすることが出来る

Ruby の case 文って クラスとオブジェクトの判定が出来る。

target = 'abc'
case target
when Array
  puts 'Array'
when Hash
  puts 'Hash'
when String
  puts 'String'
else
  puts 'else'
end

このプログラムを実行した結果は

String

となる

なんで?

マニュアルを読むと

case は一つの式に対する一致判定による分岐を行います。when 節で指定された値と最初の式を評価した結果とを演算子 === を用いて 比較して、一致する場合には when 節の本体を評価します。

http://docs.ruby-lang.org/ja/2.2.0/doc/spec=2fcontrol.html#case

となっている。

つまり、

Array === target
=> false

Hash === target
=> false

String === target
=> true

ふむふむ。この結果から String クラスだと判定してるのか。

さらにマニュアルにはこうある

また === がどのような条件で真になるかは、各クラスの === メソッ ドの動作についてのドキュメントを参照して下さい。

String クラスのマニュアルを読む。

self === other -> bool other が文字列の場合、String#eql? と同様に文字列の内容を比較します。

other が文字列でない場合、 other.to_str が定義されていれば other == self の結果を返します。(ただし、 other.to_str は実行されません。) そうでなければ false を返します。

http://docs.ruby-lang.org/ja/2.2.0/class/String.html#I_--3D--3D

え。同値判定!?

さっきの

String === target

って同値じゃないよね。

そもそも String ってクラスだよな。 String クラスのマニュアルのってインスタンスメソッドの説明なんだよなー。

。。

。。。

ふと、昔ブクマした Ruby の クラスの説明を思い出す。

クラス名というのは実は定数で(だから大文字から始まる名前じゃないといけない)、そのクラスを表すClassクラスのインスタンスへの参照を値として持つらしいです。つまり、あらゆるクラス名は実はインスタンスであり、属するクラスはClassクラスであるというわけです。

http://blog.livedoor.jp/sasata299/archives/51276137.html

つまり、String クラスは Class クラスのインスタンスであると。

じゃあ Class クラスの インスタンスメソッドをマニュアルで確認してみる。

ない。。。

じゃあ親クラスの Module クラスのマニュアルを確認してみる。

指定された obj が自身かそのサブクラスのインスタンスであるとき真を返します。 また、obj が自身をインクルードしたクラスかそのサブクラスのインスタンスである場合にも 真を返します。上記のいずれでもない場合に false を返します。

言い替えると obj.kind_of?(self) が true の場合、 true を返します。

このメソッドは主に case 文での比較に用いられます。 case ではクラス、モジュールの所属関係をチェックすることになります。

http://docs.ruby-lang.org/ja/2.2.0/class/Module.html#I_--3D--3D--3D

あった!あった!

つまり、

String はクラスであり、Class クラスのインスタンスになる。 よって、Class クラスのインスタンスメソッド(Class#=== )が呼ばれている Class#=== は再定義されていないので、 Module#=== (Class クラスの親は Module )が呼ばれている。

target は Sring クラスのインスタンスになるから、target === 'abc' と target をレシーバーにしたら String#=== が呼ばれる

じゃあ Class#=== を再定義したらどうなるか。

class Class
  def ===(other)
    false
  end
end

target = 'abc'
case target
when Array
  puts 'Array'
when Hash
  puts 'Hash'
when String
  puts 'String'
else
  puts 'else'
end

こうなる

else

再定義して false が返るから真にならない。 分かった!!!

結論

Ruby の case 文では、when の 値がレシーバー(左辺)になって比較されるので、 レシーバーのインスタンスメソッドの === メソッドが呼ばれる。

when に クラスを指定した場合は、Class クラスのインスタンスメソッド(Class#=== )が呼ばれているが、 Class#=== は再定義されていないので、 Module#=== (Class クラスの親は Module )が呼ばれている。

というわけで、case 文でクラス、モジュールの所属関係をチェックすることが出来る。

Ruby の case 文調べてたら、Ruby のクラスについても勉強になった。