再帰的なパターン マッチング
メモ
この記事は機能仕様についてです。 仕様は、機能の設計ドキュメントとして使用できます。 これには、提案された仕様の変更および機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が決定され、現在の ECMA 仕様に組み込まれるまで公開されます。
機能の仕様と行われた実装では、いくつかの違いがあることがあります。 これらの違いは、関連する言語設計ミーティング (LDM) メモに取り上げられています。
機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。
チャンピオンの課題: https://github.com/dotnet/csharplang/issues/45
まとめ
C# のパターン マッチング拡張機能を使用すると、関数型言語からの代数データ型とパターン マッチングの利点の多くが得られますが、基になる言語の感覚とスムーズに統合できます。 このアプローチの要素は、プログラミング言語である F# と Scala の関連昨日から着想を得ました。
詳細な設計
Is 式
is
演算子は、パターンに対して式をテストするように拡張されます。
relational_expression
: is_pattern_expression
;
is_pattern_expression
: relational_expression 'is' pattern
;
この形式の relational_expression は、C# 仕様の既存のフォームに追加されます。 is
トークンの左側にある relational_expression が値を指定しない場合、または型がない場合はコンパイル時エラーになります。
パターンの各識別子は、新しいローカル変数を導入します。これは、is
オペレータが true
になった後、つまり、True の場合に確実に割り当てられたに、確実に割り当てられます。
注: 技術的には、
is-expression
内の type と constant_pattern の間にあいまいさがあり、いずれも修飾された識別子の有効な解析である可能性があります。 以前のバージョンの言語との互換性のために型としてバインドしようとします。それが失敗した場合にのみ、他のコンテキストで式を行う際に、最初に見つかったもの (定数または型である必要があります) に解決します。 このあいまいさはis
式の右側にのみ存在します。
パターン
パターンは、is_pattern 演算子、switch_statement、および入力データ (入力値と呼びます) が比較されるデータの形状を表す switch_expression で使用されます。 データの一部をサブパターンと照合できるように、パターンは再帰的な場合があります。
pattern
: declaration_pattern
| constant_pattern
| var_pattern
| positional_pattern
| property_pattern
| discard_pattern
;
declaration_pattern
: type simple_designation
;
constant_pattern
: constant_expression
;
var_pattern
: 'var' designation
;
positional_pattern
: type? '(' subpatterns? ')' property_subpattern? simple_designation?
;
subpatterns
: subpattern
| subpattern ',' subpatterns
;
subpattern
: pattern
| identifier ':' pattern
;
property_subpattern
: '{' '}'
| '{' subpatterns ','? '}'
;
property_pattern
: type? property_subpattern simple_designation?
;
simple_designation
: single_variable_designation
| discard_designation
;
discard_pattern
: '_'
;
宣言パターン
declaration_pattern
: type simple_designation
;
declaration_pattern は、式が特定の型であるかをテストし、テストが成功した場合はその型にキャストする、という二つの機能を持っています。 指定が single_variable_designation の場合、指定された識別子によって指定された型のローカル変数が導入される可能性があります。 パターン マッチング操作の結果が true
の場合、そのローカル変数は確実に割り当てられます。
この式のランタイム セマンティクスは、左側の relational_expression オペランドのランタイム型を、パターン内の型に対してテストすることです。 そのランタイム型(またはサブタイプ)であり、null
ではない場合、is operator
の結果はtrue
です。
左側の静的型と指定された型の特定の組み合わせは互換性がないとみなされ、コンパイル時エラーが発生します。 静的な型である E
の値は、暗黙的参照変換、ボックス化変換、明示的参照変換、または から T
への非ボックス化変換が存在する場合、またはそれらの型の 1 つがオープン型である場合、E
の型が付随する T
と言われています。 これは、E
の方の入力が、一致するパターン内の型が付く pattern-compatible ではない場合、コンパイル時のエラーです。
型パターンは、参照型の実行時の型テストを実行するのに役立ち、イディオムを置き換えます
var v = expr as Type;
if (v != null) { // code using v
もう少し簡潔に
if (expr is Type v) { // code using v
型 が null 許容値型の場合はエラーです。
型パターンは、null 許容型の値をテストするために使用できます。Nullable<T>
型の値 (またはボックス化された T
) は、値が null 以外で、T2
の型が T
、または T
の基本型またはインターフェイスである場合に型パターン T2 id
と一致します。 たとえば、コード フラグメントで
int? x = 3;
if (x is int v) { // code using v
if
ステートメントの条件は実行時に true
で、変数 v
はブロック内で int
型の値 3
を保持します。 ブロックの後、変数 v
はスコープ内にありますが、確実には割り当てられません。
定数パターン
constant_pattern
: constant_expression
;
定数パターンは、定数値に対して式の値をテストします。 定数には、リテラル、宣言された const
変数の名前、列挙定数など、任意の定数式を指定できます。 入力値がオープン型でない場合、定数式は一致した式の型に暗黙的に変換されます。入力値の型が定数式の型とパターン互換がない場合、パターン マッチング操作はエラーになります。
変換された入力値 e は、 が object.Equals(c, e)
を返す場合、パターン true
と一致していると見なされます。
ユーザー定義の operator==
を呼び出すことができないため、新しく記述されたコードで null
をテストする最も一般的な方法として e is null
が見られると予想されます。
var パターン
var_pattern
: 'var' designation
;
designation
: simple_designation
| tuple_designation
;
simple_designation
: single_variable_designation
| discard_designation
;
single_variable_designation
: identifier
;
discard_designation
: _
;
tuple_designation
: '(' designations? ')'
;
designations
: designation
| designations ',' designation
;
指定 が 単純指定の場合、式 がパターンと一致します。 言い換えると、var パターンへの一致は、常に simple_designation で成功します。 simple_designation が single_variable_designation の場合、e の値は、新しく導入されたローカル変数にバインドされます。 ローカル変数の型は、の静的な型であるです。
指定が、tuple_designation の場合、パターンは、指定が、(var
内で確認できる)
... の positional_pattern と同等です。 たとえば、パターン var (x, (y, z))
は (var x, (var y, var z))
と同じです。
var
という名前が型にバインドされる場合はエラーです。
破棄パターン
discard_pattern
: '_'
;
と 式は、常に _
パターンに一致します。 つまり、すべての表現が破棄パターンと一致します。
破棄パターンは is_pattern_expression のパターンとして使用できません。
位置指定パターン
位置指定パターンは、入力値が null
ではないことを確認し、適切な Deconstruct
メソッドを呼び出し、結果の値に対してさらにパターン マッチングを実行します。 また、入力値の型が Deconstruct
を含む型と同じである場合、入力値の型がタプル型である場合、または入力値の型が object
または ITuple
で、式の実行時型が ITuple
を実装する場合、タプルに似たパターン構文 (型を指定せずに) もサポートします。
positional_pattern
: type? '(' subpatterns? ')' property_subpattern? simple_designation?
;
subpatterns
: subpattern
| subpattern ',' subpatterns
;
subpattern
: pattern
| identifier ':' pattern
;
型を省略すると、入力値の静的な型と見なされます。
入力値がパターン 型(
subpattern_list)
に一致する場合、メソッドは、型 で Deconstruct
のアクセス可能な宣言を検索し、分解宣言と同じ規則を使用してそのうちの 1 つを選択することによって選択されます。
positional_pattern で型を省略し、識別子を持たない単一のサブパターンを持ち、property_subpattern がなく、simple_designation がない場合はエラーです。 この文は、かっこで囲まれた constant_pattern と positional_patternの区別を明確にします。
リスト内のパターンと一致する値を抽出するには、
- 型 省略され、入力値の型がタプル型の場合、サブパターンの数はタプルのカーディナリティと同じである必要があります。 各タプル要素は対応するサブパターンと照合され、これらすべてが成功した場合に一致が成功します。 サブパターンに識別子がある場合、タプル型の対応する位置にタプル要素の名前を付けなければなりません。
- そうではなく、適切な
Deconstruct
が型のメンバーとして既存する場合で、入力値の型が型がある pattern-compatible ではない場合、コンパイル時のエラーになります。 実行時に、入力値は型に対してテストされます。 これが失敗した場合、位置指定パターンの照合は失敗します。 成功した場合、入力値はこの型に変換され、Deconstruct
はコンパイラによって生成された新しい変数を使用して呼び出され、out
パラメーターを受け取ります。 受信した各値は対応するサブパターンと照合され、これらすべてが成功した場合に一致が成功します。 サブパターンに識別子がある場合は、Deconstruct
の対応する位置にパラメーターの名前を付けなければなりません。 - それ以外では、型 が省略され、入力値が暗黙的な参照変換によって
ITuple
に変換できる型object
、ITuple
または何らかの型で、サブパターン間に識別子が表示されない場合は、ITuple
を使用して照合します。 - それ以外の場合、パターンはコンパイル時エラーです。
実行時にサブパターンが照合される順序は指定されておらず、失敗した照合はすべてのサブパターンとの照合を試みない場合があります。
例
この例では、この仕様で説明されている機能の多くを使用しています
var newState = (GetState(), action, hasKey) switch {
(DoorState.Closed, Action.Open, _) => DoorState.Opened,
(DoorState.Opened, Action.Close, _) => DoorState.Closed,
(DoorState.Closed, Action.Lock, true) => DoorState.Locked,
(DoorState.Locked, Action.Unlock, true) => DoorState.Closed,
(var state, _, _) => state };
プロパティ パターン
プロパティ パターンは、入力値が null
ではないことを確認し、アクセス可能なプロパティまたはフィールドを使用して抽出された値を再帰的に照合します。
property_pattern
: type? property_subpattern simple_designation?
;
property_subpattern
: '{' '}'
| '{' subpatterns ','? '}'
;
property_pattern のサブパターンに識別子が含まれていない場合はエラーです (識別子を持つ 2 番目の形式である必要があります)。 最後のサブパターンの後の末尾のコンマは省略可能です。
null チェック パターンは単純なプロパティ パターンから除外されることに注意してください。 文字列 s
が null 以外かどうかを確認するには、次のいずれかの形式を記述できます。
if (s is object o) ... // o is of type object
if (s is string x) ... // x is of type string
if (s is {} x) ... // x is of type string
if (s is {}) ...
式 e から パターン型{
property_pattern_list}
の一致と仮定すると、式 e が、型が指定した型 T が付く pattern-compatible でない場合、コンパイル時エラーとなります。 型が存在しない場合は、の静的な型をとみなします。 識別子が存在する場合は、型 type のパターン変数を宣言します。 property_pattern_list の左側に表示される各識別子は、T のアクセス可能で読み取り可能なプロパティまたはフィールドを指定する必要があります。property_pattern の simple_designation が存在する場合は、T 型のパターン変数を定義します。
実行時に、式は T に対してテストされます。これが失敗した場合、プロパティ パターンの照合は失敗し、結果は false
になります。 成功した場合、各 property_subpattern フィールドまたはプロパティが読み取られ、その値が対応するパターンに対して照合されます。 試合全体の結果が false
になるのは、これらのいずれかの結果が false
である場合のみです。 サブパターンが照合される順序は指定されておらず、失敗した照合は実行時にすべてのサブパターンとの照合を試みない場合があります。 正常に一致し、property_pattern の simple_designation が、single_variable_designation の場合、一致する値を割り当てる T の型の変数が定義されます。
注: プロパティ パターンは匿名型とのパターン マッチングに使用できます。
例
if (o is string { Length: 5 } s)
Switch 式
switch_expression は、式コンテキストの switch
のようなセマンティクスをサポートするために追加されます。
C# 言語の構文は、以下の構文規則によって拡張されています。
multiplicative_expression
: switch_expression
| multiplicative_expression '*' switch_expression
| multiplicative_expression '/' switch_expression
| multiplicative_expression '%' switch_expression
;
switch_expression
: range_expression 'switch' '{' '}'
| range_expression 'switch' '{' switch_expression_arms ','? '}'
;
switch_expression_arms
: switch_expression_arm
| switch_expression_arms ',' switch_expression_arm
;
switch_expression_arm
: pattern case_guard? '=>' expression
;
case_guard
: 'when' null_coalescing_expression
;
switch_expression は expression_statement として許可されていません。
今後の改訂でこれを緩和する予定です。
switch_expression の型は、switch_expression_arm の トークンの右に表示される式の=>
(§12.6.3.15) です。ただし、そのような型が存在し、switch 式の各アーム内の式が、暗黙的にその型に変換できる場合に限ります。 さらに、新しい switch 式変換を追加しました。これは、switch 式から各型の T
に事前定義された暗黙的な変換で、格アームの式から T
に暗黙的に変換されます。
一部の以前のパターンとガードが常に一致するため、ある switch_expression_arm パターンが、結果に影響しない場合は、エラーとなります。
switch 式の一部のアームが、その入力の各値を処理する場合、switch 式は、は、徹底的とみなされます。 スイッチ式が完全でない場合、コンパイラは警告を生成します。
実行時に、switch_expression の結果が、最初の switch_expression_arm の式の値で、switch_expression の左側の式が、switch_expression_arm のパターンと一致し、存在する場合は、switch_expression_arm の case_guard は、true
と評価されます。 このような switch_expression_arm がない場合、switch_expression は例外 System.Runtime.CompilerServices.SwitchExpressionException
のインスタンスをスローします。
タプルリ テラルを切り替える際の省略可能な括弧
switch_statement を使用してタプル リテラルを切り替えるには、冗長な括弧のように見えるものを記述する必要があります
switch ((a, b))
{
許可するには
switch (a, b)
{
切り替えられた式が タプル リテラルの場合、switch 文の括弧は省略可能です。
パターン マッチングでの評価の順序
パターン マッチング中に実行される操作をコンパイラで柔軟に並べ替えることができるので、パターン マッチングの効率を向上させるために使用できる柔軟性を実現できます。 (強制されていない) 要件は、パターンでアクセスされるプロパティと Deconstruct メソッドが "純粋" (副作用のない、冪等性など) である必要があるという要件です。 これは、言語の概念として純度を追加することを意味するわけではありません。これは、コンパイラが操作を柔軟に並べ替えることを可能にすることだけです。
解決策 2018-04-04 LDM: 確認済み: コンパイラは、Deconstruct
、プロパティ アクセス、および ITuple
内のメソッドの呼び出しの呼び出しを並べ替えることができます。また、複数の呼び出しから返される値が同じであると想定される場合があります。 コンパイラは結果に影響を与えない関数を呼び出すべきではありません。今後、コンパイラによって生成される評価順序を変更する前に十分に注意してください。
考えられるいくつかの最適化
パターン マッチングのコンパイルでは、パターンの一般的な部分を利用できます。 たとえば、switch_statement 内の 2 つの連続するパターンの最上位の型テストが同じ型である場合、生成されたコードは 2 番目のパターンの型テストをスキップできます。
一部のパターンが整数または文字列である場合、コンパイラは、以前のバージョンの言語の switch ステートメントに対して生成するのと同じ種類のコードを生成できます。
このような最適化の詳細については、[Scott and Ramsey (2000)]。
C# feature specifications