Ruby2.7.0-devのパターンマッチでポーカーの役を判定してみた

Ruby2.7ではパターンマッチが入るらしいと聞いたので、Ruby2.7.0-devでポーカーの役のチェックを書いてみました。
このコードは今の所Ruby2.7.0-devでしか動きませんし、もしかすると将来的にもRuby2.7.0-devでしか動かないかもしれません。あしからず。

# コード

出来上がったコードはこんな感じで、

```ruby
class PokerChecker
  def check(cards)
    case cards.sort { @1[1] <=> @2[1] }
      in [[m, a], [^m, b], [^m, c], [^m, d], [^m, e]] if a != 1 && [a, b, c, d].zip([b, c, d, e]).map { @2 - @1 }.all? { @1 == 1 } ; :straight_flush
      in [[m, 1], [^m, 10], [^m, 11], [^m, 12], [^m, 13]]; :straight_flush
      in [[_, a], [_, ^a], [_, ^a], [_, ^a], [_, _]]; :four_of_kind
      in [[_, _], [_, a], [_, ^a], [_, ^a], [_, ^a]]; :four_of_kind
      in [[_, a], [_, b], [_, c], [_, d], [_, e]] if a != 1 && [a, b, c, d].zip([b, c, d, e]).map { @2 - @1 }.all? { @1 == 1 } ; :straight
      in [[_, 1], [_, 10], [_, 11], [_, 12], [_, 13]]; :straight
      in [[m, _], [^m, _], [^m, _], [^m, _], [^m, _]]; :flush
      in [[_, a], [_, ^a], [_, b], [_, ^b], [_, ^b]]; :full_house
      in [[_, a], [_, ^a], [_, ^a], [_, b], [_, ^b]]; :full_house
      in [[_, a], [_, ^a], [_, ^a], [_, _], [_, _]]; :three_of_kind
      in [[_, _], [_, a], [_, ^a], [_, ^a], [_, _]]; :three_of_kind
      in [[_, _], [_, _], [_, a], [_, ^a], [_, ^a]]; :three_of_kind
      in [[_, a], [_, ^a], [_, b], [_, ^b], [_, _]]; :two_pair
      in [[_, _], [_, a], [_, ^a], [_, b], [_, ^b]]; :two_pair
      in [[_, a], [_, ^a], [_, _], [_, b], [_, ^b]]; :two_pair
      in [[_, a], [_, ^a], [_, _], [_, _], [_, _]]; :one_pair
      in [[_, _], [_, a], [_, ^a], [_, _], [_, _]]; :one_pair
      in [[_, _], [_, _], [_, a], [_, ^a], [_, _]]; :one_pair
      in [[_, _], [_, _], [_, _], [_, a], [_, ^a]]; :one_pair
      else; :buta
    end
  end
end
```

使うときはこんな感じです。

```irb
irb(main):003:0> PokerChecker.new.check([[:s, 2], [:d, 2], [:h, 2], [:c, 8], [:h, 8]])
=> :full_house
```

checkメソッドに配列の配列を渡していて、中身の配列の1つ目がトランプのマーク、2つ目がトランプの数値を表しています。上の例だと、スペードの２、ダイヤの２、ハートの２、クラブの８、ハートの８でフルハウスとなっています。

ストレートフラッシュの判定でいきなり ``if`` の力に頼っているのがいささか残念ですが、20行ちょっとで書けていることからパターンマッチの強力さが伺えます。（[テストも書いた](https://github.com/t-kunou/poker_checker/blob/master/poker_checker_spec.rb)ので多分バグは無いと思いますが、バグってたらすみません）

パターンマッチの記法に慣れないうちは面食らうと思いますが、慣れてくれば気にならなくなるのではないでしょうか（きっと）。

書いていてなかなか楽しかったので、早く仕事でも使えるようになると良いですね。

# その他所感

### ストレート（とストレートフラッシュ）の判定について

本当は

```ruby
in [[_, a], [_, ^a + 1], [_, ^a + 2], [_, ^a + 3], [_, ^a + 4]]; :straight
```

のように書きたかったのですが、これはSyntaxErrorになってしまいました。

```
SyntaxError ((irb):9: syntax error, unexpected '+', expecting ']')
in [[_, a], [_, ^a + 1], [_, ^a + 2], [_, ^a + 3]...
                   ^
```

``^a + 1`` を括弧で囲んでも変わらなかったので、諦めて連続しているかの判定を ``if`` で書いています。

### Arrayパターンで変数へ代入しつつ複数条件書く時

パターンマッチでは、 ``|`` 区切りで複数のパターンが書けます。

```irb
irb(main):018:0> case 3; in 1 | 2 | 3; :match; end
=> :match
```

これはArrayパターンでも同様に使えます。

```irb
irb(main):016:0> case [1, 2, 2]; in [1, 2, 2] | [2, 2, 1]; :match; end
=> :match
```

が、変数への代入も合わせて書くと…

```irb
irb(main):017:0> case [1, 2, 2]; in [_, a, ^a] | [a, ^a, _]; :match; end
Traceback (most recent call last):
        1: from (irb)
SyntaxError ((irb):17: illegal variable in alternative pattern (a))
(irb):17: illegal variable in alternative pattern (a)
```

と、これまたSyntaxErrorになってしまいました。

```irb
irb(main):019:0> case [1, 2, 2]; in [_, a, ^a]; :match; end
=> :match
```

↑は動くので意図的な物なのか何なのかよく分かりません。

ただし、分解して別の``in`` にすれば良いですし、かえって分解したほうが読みやすくなったので結果的には良かったです。


### Numbered arguments

ついでにNumbered argumentsも使ってみました。一行に収まる程度のブロック(またはProc)であれば、可読性を保ったまま短くかけそうです。

### おまけ

本文中では読みやすさのために消していますが、パターンマッチを書くと ``warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!`` というありがたい警告が出ました。