7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ruby on RailsAdvent Calendar 2024

Day 19

【Ruby】2つの配列の状態に応じて条件分岐する処理をパターンマッチで書き直してみた

Last updated at Posted at 2024-12-23

はじめに

先日、仕事で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

引数のguitarsampsは配列で、

  • 両方あるとき
  • ギターが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章ぶんまるごと使ってパターンマッチを詳しく、丁寧に説明しています。

Screenshot 2024-12-16 at 11.58.44.png

パターンマッチ、名前は知ってるけど全然わからない😣という人は、ぜひ「プロを目指す人のためのRuby入門 改訂2版」を読んでみてください!

ちなみに、パターンマッチが解説されているのは、さくらんぼが2つの「改訂2版」の方なので、間違って第1版を買わないように注意してくださいね!

20211201085815.png

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?