Ruby 2.7 の変更点 - パターンマッチング

Ruby 2.7 アドベントカレンダーの2日目の記事です。(更新が遅いのは仕様です)

qiita.com

パターンマッチング

パターンマッチングは Ruby 2.7 での目玉機能と言ってもいいでしょう。 ただし現時点では experimental で、使用すると次のメッセージが出力されます。

warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!

パターンマッチングは配列やHashなどの構造のパターンとオブジェクトをマッチングするためのものです。

パターンマッチングは case...in 構文を使用します。

case に指定したオブジェクトのパターンが、in で指定したパターンと一致していれば、パターンに指定した変数に対応する値が代入されて、in 部が実行されます。

obj = [0, [1, 2, 3]] case obj in [a, [b, *c]] p a #=> 0 p b #=> 1 p c #=> [2, 3] end 

配列の場合は要素数が一致していないとマッチしませんが、Hash の場合はパターンに書かれたキーが適合すれば、オブジェクトに他のキーが存在していても構いません。

obj = {a: 0, b: 1, c: 2} case obj in {a: 0, x: 1} # ここは通らない in {a: 0, b: var} p var #=> 1 end 

JSONをパースした結果の構造から値を取り出す時に便利ですね。

require 'json' json = '{"a":123, "b":[0, {"c":456, "d":{"e": 789}}]}' case JSON.parse(json, symbolize_names: true) in {b: [_, {c: _, d: {e: var}}]} p var #=> 789 end 

適合するパターンが無ければエラーになります。

obj = [1, "hoge"] case obj in {a: var} # 適合しない in [a, b, c] # 適合しない end #=> [1, "hoge"] (NoMatchingPatternError) 

else を書いておくとエラーにならずに else 部が実行されます。

obj = [1, "hoge"] case obj in {a: var} # 適合しない in [a, b, c] # 適合しない else "no match" end #=> "no match" 

さらにパターンには ifunless で条件を追加することもできます。

def hoge(obj) case obj in [a, b] if a == b.to_i true else false end end hoge [123, "123"] #=> true hoge [123, "456"] #=> false hoge "xxxx" #=> false 

パターンに変数を使用したい場合は ^ をつけて記述します。

def hoge(obj) pattern = [1, 2] case obj in ^pattern true else false end end hoge [1, 2] #=> true hoge [3, 4] #=> false 

型を指定することもできます。その場合は => で割り当てる変数を指定します。

def hoge(obj) case obj in [String => s, Integer => n] s * n in [a, b] a + b end end hoge ["abc", 2] #=> "abcabc" hoge [3, 4] #=> 7 

その他、詳しい情報は RubyKaigi 2019 の発表資料を見ましょう。

rubykaigi.org

[追記] どうやら case の中ではなく in だけでも使えるようです。

obj = [123, "abc"] obj in [a, b] p a #=> 123 p b #=> "abc" 

obj in pattern はマッチしたかどうかを真偽値で返すので、if で使えて便利。

obj = [123, "abc"] if obj in [a, b] p a #=> 123 p b #=> "abc" end 

[追記の追記] これは 2.7.0-rc1 でできなくなりました。真偽値を返さなくなり、マッチしなかった場合に例外があがります。


というわけで便利そうなんですけど、個人的にはなんかモニョってたり。

in の後ろはパターンだと思えばいいのかもしれないけど、Ruby には for...in というのがあって、この場合は従来どおりの配列なんですな。

for v in [1, 2, 3] p v end 

まあでも for は誰も使ってないからいいや(暴言)。

case...when と形式が似てるのがアレなのかな…。

case obj when Array # obj が Array であれば実行 end case obj in Array # obj が Array であれば実行 end 

あれ? 同じだな…。じゃあ case の一種でいいのか。

やっぱりパターン内に変数があるのがしっくりこないのかな。

[var] という表記は、今までの Ruby の構文だと var オブジェクトを要素として持つ配列を表していたので、当然 var はそれよりも前に宣言された変数でないとエラーになってたんだけど、in [var] はパターンなので、var は変数参照ではなくパターンにマッチした時に値が代入される変数として扱われる…というのが。 突然未定義変数っぽい字面のものが表れてびっくりするというか…。

代入っぽくないのに変数に値が代入されるものとしては、既に正規表現の名前付きキャプチャなんてのもあるけど…。

/(?<hoge>[a-z]*)(?<fuga>[0-9]*)/ =~ 'abc123' hoge #=> "abc" fuga #=> "123" 

やっぱり慣れなのかな。一年くらい経ったら普通に便利に使ってるような気もしなくもない。

コメントを書く

Ruby 2.7 の変更点 - パターンマッチング

プロフィール
注目記事