成らぬは人の為さぬなりけり

エンジニアライフをエンジョイする為のブログ

case式の対象オブジェクトのメソッドの戻り値を条件に使用する

うん、タイトルだけだと、意味わからん、という感じかと、、、
わかるかな?
なんて書いたらいいのか、悩んだので、
とりあえず、書いてみました。


コードで示した方が分かりやすいかと。
※やりたいと思った事(以下のコードは間違ってます)

case hoge
when foo?
 (hoge.foo?がtrueだった時の処理)
when "bar"
 ...
end

みたいな事がやりたかったのです。


しかし、まぁ、当然、↑のコードはエラーになります。
さて、どうしたものか。
こんなことはできないのでしょうか?

if hoge.foo?
 ...
else
  case hoge
  when "bar"
    ...
  end
end

このコード、ダサくないですか?
クールじゃないですよね。


調べました。
結果、できました。
※以下のコードはRuby1.9以降

case hoge
when :foo?.to_proc
 ...
when "bar"
 ...
end

これがクールかと言われると、微妙な所ですが、
少なくとも、ifで分岐するよりはスマートかと思います。


さて、では、なぜ↑の式は成り立つのでしょうか?


case式は、制御構造にもあるとおり
===演算子を使ったif〜elsif〜end式とほぼ等価な処理を行うように実装されています。
つまり、

if :foo?.to_proc === hoge
 ...
elsif "bar" === hoge
 ...
end

と、ほぼ等価なんですね。

ここからが大切です。

まずは、「Object#===」って何?って事です。
class Object
ここを見てみると、
サブクラスで再定義されない限りは、「Object#==」を呼び出すだけのようです。
では、再定義されてる組み込みクラスはあるのか? あります。
以下の4つです。

  • Module
    • kind_of?を呼んでいるだけ
  • Proc
    • callの別名
  • Range
    • include?を呼んでいるだけ
  • Regexp
    • =~やmatchとほぼ同じ

以前、ブログに書きましたが、
Symbolのto_procで生成されるProcオブジェクトは、
callの引数に渡されたオブジェクトをレシーバとして、
シンボル名と同じ名前のメソッドを呼び出します。
そして、↑にもあるとおり、
Procの===はcallの別名です。
つまり、

:foo?.to_proc === hoge

:foo?.to_proc.call(hoge)

ということになるわけです。
なので、

case hoge
when :foo?.to_proc
 ...①
when "bar"
 ...②
end

これで、foo?がtrueであれば、①の部分が評価されるわけです。

うん。納得。