yashiganiの英傑になるまで死ねない日記

週末はマスターバイクでハイラルを走り回ります

Rubyのcaseを分解してみた

@さんがPythonなら不等式を連結できることに喜んでいて,たまたまここに書いてるのを読んでたから「それRubyでもできるよ」って反応した.

「どうなってんの?」って,聞かれたれたけどRuby力低くてなんで動いてるのか答えられなかった.けれども,調べているうちに理解できたのでメモ.

このコードを元に考えてみる.

n = 5
status =
case n
when 1..10
  :first
when 11..20
  :second
end

puts status # => :first

これ初見でも直感的に動作は理解はできるけど,実際書けって言われたら絶対無理.どうなってるのか分解してみる.

1..10

いうまでもないけど,1..10はRangeオブジェクトを返す.ここもオブジェクトなのが大事.これはメソッドなのかな?そこまでは調べられてない.

case

caseが値返しててまず「なんじゃこら」ってなるけどRubycaseは式なので値が返ってくる.(式ってなんやって人は黙ってHaskellを…)この式から返ってくる値は最後に評価した値らしい.メソッドの動きと同じ.そしてwhen節に与えられた式とcaseに与えられた値を===で評価して,真ならその節を評価する.

つまり,caseの動作は以下のコードと同じになる.

n = 5
status =
if (1..10) === n
  :first
elsif (11..20) === n
  :second
end

puts status # => :first

あ,Rubyではもちろんifも式.

===ってなに?

===ってなんや?Rangeオブジェクトのリファレンスを見てみると,Range#===Range#include?と同じ動きをするこようだ.include?なら一目で動作がわかる. (1..10) === nとかどう見てもメソッド呼び出しじゃないだろバカめ!と思うかもしれないが,Rubyではメソッド引数の()は省略できる.更にメソッド名が記号の場合,メソッドの前のドットさえも省略できる.Haskellの中置みたいな感じ.

つまり,以下のように書き換えできる.

n = 5
status =
if (1..10).include?(n)
  :first
elsif (11..20).include?(n)
  :second
end

puts status # => :first

ここまでくると動作の仕組みは誰の目にも明らかだ.caseがなんで動いてるのか理解できたぞ.

感想

適宜===さえ実装してやれば,自分で定義したクラスにもcaseが適用できるのがいいと思う.既にあるものに関しても差し替えてやればいいし(List#===は与えられたリストと同値かを返すけど,要素を含んでいるかという動作に書き換えるのは簡単). にしても,Rubyはシンタックスシュガーが多くてわからないことが多い.ドキュメントにもなんの説明もなく平気で書いてあるし.けど,どういう理屈で動作しているのか,きちんと調べていくと発見があっておもしろい.

まとめ

  • Pythonじゃなくても不等式重ねられる.Rubyのはかなり柔軟に使えそう
  • ===は便利.たぶんもっと他の場所でも使ってる
  • Ruby暗黙知っぽいのが多いけど,なんなのか想像しつつ読むとおもしろい
  • 仕組みを調べると言語自体の理解が深まる気がする

関連