はじめに
先日、仕事でRailsアプリのコードレビューをしているときに、こんなメソッドに遭遇しました(記事用にコードは単純化してあります)。
def shop_status(guitars, amps)
if guitars.size > 0 && amps.size > 0
"機材がいっぱいあるよ"
elsif guitars.size == 1
"ギターが1本あるよ"
elsif guitars.size > 1
"ギターがいっぱいあるよ"
elsif amps.size == 1
"アンプが1台あるよ"
elsif amps.size > 1
"アンプがいっぱいあるよ"
else
"機材が何もないよ"
end
end
引数のguitars
とamps
は配列で、
- 両方あるとき
- ギターが1本のとき(アンプは0台)
- ギターが2本以上のとき(アンプは0台)
- アンプが1台のとき(ギターは0本)
- アンプが2台以上のとき(ギターは0本)
- 両方0のとき
の6パターンで条件分岐しています。
良くも悪くも愚直な実装でこのままでもいいような気もしますが、こういうケースはパターンマッチで条件分岐した方がわかりやすいんじゃないかな?と思ったので、今回はその方法を紹介してみます。
パターンマッチで書き直してみる
というわけで、実際にパターンマッチを使って書き直したのが以下のコードです。
def shop_status(guitars, amps)
case [guitars, amps]
in [[_guitar, *], [_amp, *]]
"機材がいろいろあるよ"
in [[_guitar], []]
"ギターが1本あるよ"
in [[_guitar, *], []]
"ギターがいっぱいあるよ"
in [[], [_amp]]
"アンプが1台あるよ"
in [[], [_amp, *]]
"アンプがいっぱいあるよ"
else
"機材が何もないよ"
end
end
どうでしょう?
「[[_guitar, *], [_amp, *]]
はギターもアンプも1つ以上あるんだな」
とか、
「[[], [_amp]]
はギターが0本で、アンプが1台か」
というように、パターンマッチで表現すると、コードを通して配列の中身が透けて見えるような感じがしませんか?
「ギターが2本以上」をどう表現するか?
上のコードで「ギターが2本以上ある」という条件は、
in [[_guitar, *], []]
"ギターがいっぱいあるよ"
と書きました。
この条件単体だと「ギターが1本以上」を表すのですが、1つ手前に「in [[_guitar], []]
」という条件があります。
パターンマッチは上から順番に評価されていくため、これで「ギターが1本の場合」は除外されます。
ゆえに、in [[_guitar, *], []]
は自ずと「ギターが2本以上」という条件を表すことになります。
ですが、あえて次のように書いた方が「ギターが2本以上」ということが直感的にわかるかもしれません。
in [[_guitar, _guitar, *], []]
"ギターがいっぱいあるよ"
どちらがいいかは好みの問題ですね。
([_guitar, _guitar, *]
はちょっとくどい感じがしたので、今回は[_guitar, *]
にしました)
「何もないケース」もin + パターンで表現してみる
また、最後のelseの部分は以下のように「in
+ パターン」の形式で書くのも良いと思います。
こっちの方が「どっちも空だよ、何にもないよ」という条件であることが伝わかりやすいかもしれませんね。
in [[], []]
"機材が何もないよ"
end
なお、Rubyのパターンマッチはどのパターンにもマッチしなかったときは例外が発生するので、万が一これ以外の条件が存在していた場合もすぐに気付きやすいです。
つまり、以下のように明示的にelse節を用意して例外を発生させる必要がありません。
in [[], []]
"機材が何もないよ"
else
# こんなふうにelse節で明示的に例外を発生させなくてもよい
raise "予想外の条件が存在していたようだ"
end
_ だけにするのもOK
上のコード例では _guitar
や _amp
のように、アンダースコアで始まる変数名を使っています。
これは「変数としては利用しないが、ギターなのかアンプなのかを明確にした方が可読性が高いのでは?」と考えたからです。
_guitar
や_amp
は、ただの_
にしても動作には違いがないので、好みに応じて_
だけにするのもアリかと思います。
def shop_status(guitars, amps)
case [guitars, amps]
in [[_, *], [_, *]]
"機材がいろいろあるよ"
in [[_], []]
"ギターが1本あるよ"
in [[_, *], []]
"ギターがいっぱいあるよ"
in [[], [_]]
"アンプが1台あるよ"
in [[], [_, *]]
"アンプがいっぱいあるよ"
else
"機材が何もないよ"
end
end
_
だけにした場合であれば、「ギターが2本以上」という条件を、
in [[_, _, *], []]
"ギターがいっぱいあるよ"
と書いても、くどさは感じないかもしれません。
みなさんはどの書き方が一番読みやすいでしょうか?
まとめ
この記事では2つの配列の状態に応じて条件分岐する処理をパターンマッチで書き直すコード例を紹介してみました。
配列やハッシュの構造に応じて条件分岐したい、という用途ではパターンマッチが活用できることが多いので、日々のコーディングでパターンマッチが使えそうな処理がないか探してみてください!
あわせて読みたい
パターンマッチをテストコードに応用してみた、という記事です。
こちらもあわせてどうぞ〜。
【PR】Rubyのパターンマッチを学ぶならこれ!
Rubyのパターンマッチはうまく使いこなせると非常に強力な武器になるのですが、かなり高機能なぶん、仕様(構文)が複雑です。
拙著「プロを目指す人のためのRuby入門 改訂2版」では、1章ぶんまるごと使ってパターンマッチを詳しく、丁寧に説明しています。
パターンマッチ、名前は知ってるけど全然わからない😣という人は、ぜひ「プロを目指す人のためのRuby入門 改訂2版」を読んでみてください!
ちなみに、パターンマッチが解説されているのは、さくらんぼが2つの「改訂2版」の方なので、間違って第1版を買わないように注意してくださいね!