Rubyでのパターンマッチングの概要
Rubyでのパターンマッチング、その機能、およびコードの可読性の向上にどのように役立つかについての簡単な説明から始めましょう。
数年前の私のような人なら、正規表現のパターンマッチングと混同するかもしれません。他のコンテキストがない「パターンマッチング」をGoogleですばやく検索しても、その定義にかなり近いコンテンツが表示されます。
正式には、パターンマッチングは、データ(文字のシーケンス、一連のトークン、タプルなど)を他のデータと照合するプロセスです。
プログラミングに関しては、言語の機能に応じて、これは次のいずれかを意味する可能性があります。
- 予想されるデータ型との照合
- 予想されるハッシュ構造(特定のキーの存在など)との照合
- 予想される配列の長さとの一致
- 一致(またはそれらの一部)をいくつかの変数に割り当てる
パターンマッチングへの私の最初の進出は、Elixirを介したものでした。 Elixirはパターンマッチングをファーストクラスでサポートしているため、 =
実際、演算子は match
単純な割り当てではなく、演算子。
これは、Elixirでは、以下が実際に有効なコードであることを意味します。
iex> x = 1
iex> 1 = x
そのことを念頭に置いて、Ruby 2.7以降の新しいパターンマッチングのサポートと、それを使用してコードを読みやすくする方法を、今日から見ていきましょう。
case
とのルビーパターンマッチング / in
Rubyは、特別な case
を使用したパターンマッチングをサポートしています。 / in
表現。構文は次のとおりです。
case <expression>
in <pattern1>
# ...
in <pattern2>
# ...
else
# ...
end
これをcase
と混同しないでください / when
表現。 when
およびin
ブランチを単一のcase
に混在させることはできません 。
else
を指定しない場合 式では、一致しない場合は NoMatchingPatternError
が発生します 。
Rubyのパターンマッチング配列
パターンマッチングを使用して、データ型、長さ、または値に対して、配列を事前に必要な構造に一致させることができます。
たとえば、次のすべてが一致します(の最初ののみに注意してください)
case
として評価されます 最初の試合の世話をやめます):
case [1, 2, "Three"]
in [Integer, Integer, String]
"matches"
in [1, 2, "Three"]
"matches"
in [Integer, *]
"matches" # because * is a spread operator that matches anything
in [a, *]
"matches" # and the value of the variable a is now 1
end
このタイプのパターンマッチング句は、メソッド呼び出しから複数のシグナルを生成する場合に非常に役立ちます。
Elixirの世界では、これは:ok
の両方を持つ可能性のある操作を実行するときに頻繁に使用されます 結果と:error
たとえば、データベースに挿入された結果。
読みやすくするために使用する方法は次のとおりです。
def create
case save(model_params)
in [:ok, model]
render :json => model
in [:error, errors]
render :json => errors
end
end
# Somewhere in your code, e.g. inside a global helper or your model base class (with a different name).
def save(attrs)
model = Model.new(attrs)
model.save ? [:ok, model] : [:error, model.errors]
end
Rubyのパターンマッチングオブジェクト
Rubyでオブジェクトを照合して、特定の構造を適用することもできます。
case {a: 1, b: 2}
in {a: Integer}
"matches" # By default, all object matches are partial
in {a: Integer, **}
"matches" # and is same as {a: Integer}
in {a: a}
"matches" # and the value of variable a is now 1
in {a: Integer => a}
"matches" # and the value of variable a is now 1
in {a: 1, b: b}
"matches" # and the value of variable b is now 2
in {a: Integer, **nil}
"does not match" # This will match only if the object has a and no other keys
end
これは、パラメータとの照合に強力なルールを課す場合に最適です。
たとえば、派手なグリーターを書いている場合、次の(強く意見のある)構造になる可能性があります。
def greet(hash = {})
case hash
in {greeting: greeting, first_name: first_name, last_name: last_name}
greet(greeting: greeting, name: "#{first_name} #{last_name}")
in {greeting: greeting, name: name}
puts "#{greeting}, #{name}"
in {name: name}
greet(greeting: "Hello", name: name)
in {greeting: greeting}
greet(greeting: greeting, name: "Anonymous")
else
greet(greeting: "Hello", name: "Anonymous")
end
end
greet # Hello, Anonymous
greet(name: "John") # Hello, John
greet(first_name: "John", last_name: "Doe") # Hello, John Doe
greet(greeting: "Bonjour", first_name: "John", last_name: "Doe") # Bonjour, John Doe
greet(greeting: "Bonjour") # Bonjour, Anonymous
Rubyでの変数のバインドと固定
上記の例のいくつかで見たように、パターンマッチングは、パターンの一部を任意の変数に割り当てるのに非常に役立ちます。これは変数バインディングと呼ばれ、変数にバインドする方法はいくつかあります。
- タイプが強く一致する場合(例:
in [Integer => a]
またはin{a:Integer => a}
- タイプ指定なし、例:
in [a、1、2]
または{a:a}の。
- 変数名なし。デフォルトではキー名を使用します。例:
in {a:}
a
という名前の変数を定義します キーa
に値があります 。 - 残りをバインドします。例:
in [Integer、* rest]
またはin{a:Integer、** rest}
。
では、既存の変数をサブパターンとして使用する場合、どのように一致させることができますか?これは、変数 pinningを使用できる場合です。 ^
で (ピン)演算子:
a = 1
case {a: 1, b: 2}
in {a: ^a}
"matches"
end
変数がパターン自体で定義されている場合でもこれを使用できるため、次のような強力なパターンを記述できます。
case order
in {billing_address: {city:}, shipping_address: {city: ^city}}
puts "both billing and shipping are to the same city"
else
raise "both billing and shipping must be to the same city"
end
変数のバインドで言及する重要な癖の1つは、パターンが完全に一致していなくても、変数がバインドされたままになることです。これは便利な場合があります。
ただし、ほとんどの場合、これは微妙なバグの原因にもなる可能性があります。したがって、試合内で使用されたシャドウ変数の値に依存しないようにしてください。たとえば、次のように、都市は「アムステルダム」になりますが、代わりに「ベルリン」になります:
city = "Amsterdam"
order = {billing_address: {city: "Berlin"}, shipping_address: {city: "Zurich"}}
case order
in {billing_address: {city:}, shipping_address: {city: ^city}}
puts "both billing and shipping are to the same city"
else
puts "both billing and shipping must be to the same city"
end
puts city # Berlin instead of Amsterdam
Rubyのカスタムクラスのマッチング
いくつかの特別なメソッドを実装して、Rubyでカスタムクラスのパターンマッチングを認識させることができます。
たとえば、ユーザーを first_name
とパターンマッチングするには およびlast_name
、 deconstruct_keys
を定義できます クラスで:
class User
def deconstruct_keys(keys)
{first_name: first_name, last_name: last_name}
end
end
case user
in {first_name: "John"}
puts "Hey, John"
end
キー
deconstruct_keys
への引数 パターンで要求されたキーが含まれています。これは、すべてのキーの計算にコストがかかる場合に、受信者が必要なキーのみを提供する方法です。
deconstruct_keys
と同じように 、 deconstruct
の実装を提供できます オブジェクトを配列としてパターンマッチングできるようにします。たとえば、 Location
があるとします。 緯度と経度を持つクラス。 deconstruct_keys
の使用に加えて 緯度と経度のキーを提供するために、 [latitude、longitude]
の形式で配列を公開できます。 同様に:
class Location
def deconstruct
[latitude, longitude]
end
end
case location
in [Float => latitude, Float => longitude]
puts "#{latitude}, #{longitude}"
end
複雑なパターンにガードを使用する
通常のパターン一致演算子では表現できない複雑なパターンがある場合は、 if
を使用することもできます。 (またはただし
)試合のガードを提供するステートメント:
case [1, 2]
in [a, b] if b == a * 2
"matches"
else
"no match"
end
=>
とのパターンマッチング / in
case
なし
Ruby 3以降を使用している場合は、さらに多くのパターンマッチングの魔法にアクセスできます。 Ruby 3以降、パターンマッチングはcaseステートメントなしで1行で実行できます:
[1, 2, "Three"] => [Integer => one, two, String => three]
puts one # 1
puts two # 2
puts three # Three
# Same as above
[1, 2, "Three"] in [Integer => one, two, String => three]
上記の構文にelse
がない場合 句、データ構造が事前にわかっている場合に最も役立ちます。
例として、このパターンは、管理者ユーザーのみを許可するベースコントローラー内にうまく適合する可能性があります。
class AdminController < AuthenticatedController
before_action :verify_admin
private
def verify_admin
Current.user => {role: :admin}
rescue NoMatchingPatternError
raise NotAllowedError
end
end
Rubyでのパターンマッチング:このスペースを見る
最初は、パターンマッチングを把握するのが少し奇妙に感じることがあります。一部の人にとっては、栄光のオブジェクト/配列の脱構築のように感じるかもしれません。
しかし、Elixirの人気が何らかの兆候であるとすれば、パターンマッチングはあなたの武器庫にある素晴らしいツールです。Elixirでそれを直接使用した経験があるので、一度慣れないと生きていくのは難しいことを確認できます。
Ruby 2.7を使用している場合は、パターンマッチング( case
を使用) / in
)はまだ実験的です。 Ruby 3では、 case
/ in
新しく導入された単一行のパターンマッチング式は実験的なものですが、は安定版に移行しました。警告は Warning [:experimental] =false
でオフにできます。 コードまたは-W:no-experimental
コマンドラインキー。
Rubyでのパターンマッチングはまだ初期段階ですが、この紹介がお役に立てば幸いです。また、今後の開発について私と同じように興奮していることを願っています。
P.S。 Ruby Magicの投稿が報道されたらすぐに読みたい場合は、Ruby Magicニュースレターを購読して、投稿を1つも見逃さないでください。
-
Ruby2.6の9つの新機能
Rubyの新しいバージョンには、新しい機能とパフォーマンスの改善が含まれています。 変更についていきますか? 見てみましょう! 無限の範囲 Ruby 2.5以前のバージョンは、すでに1つの形式の無限範囲をサポートしています( Float ::INFINITY を使用) )、しかしRuby2.6はこれを次のレベルに引き上げます。 新しい無限の範囲 次のようになります: (1..) これは、(1..10)のような終了値がないため、通常の範囲とは異なります。 。 使用例 : [a, b, c].zip(1..) # [[a, 1], [b, 2], [c, 3]] [1,2,3,
-
Rubyのデコレータデザインパターン
デコレータのデザインパターンは何ですか? そして、Rubyプロジェクトでこのパターンをどのように使用できますか? デコレータデザインパターンは、新機能を追加することでオブジェクトを強化するのに役立ちます クラスを変更せずにそれに。 例を見てみましょう! ロギングとパフォーマンス この例では、rest-clientのようなgemを使用してHTTPリクエストを作成しています。 次のようになります: require restclient data = RestClient.get(www.rubyguides.com) 今 : 一部のリクエストにログを追加したいが、RestCli