目次

パターン

パターンは、Dart 言語におけるステートメントや式のような構文カテゴリです。パターンは、実際の値と照合する可能性のある値の集合の形状を表します。

このページでは以下について説明します。

  • パターンがすること。
  • Dart コードでパターンが許可される場所。
  • パターンに関する一般的なユースケース。

さまざまな種類のパターンについては、パターンの種類ページを参照してください。

パターンがすること

#

一般的に、パターンは、コンテキストとパターンの形状に応じて、値をマッチングしたり、値を構造分解したり、またはその両方を実行したりすることがあります。

まず、パターンマッチングでは、特定の値を以下のように確認できます。

  • 特定の形状を持っているかどうか。
  • 特定の定数であるかどうか。
  • 他の何かと等しいかどうか。
  • 特定の型を持っているかどうか。

次に、パターン構造分解を使用すると、その値を構成要素に分解するための便利な宣言構文を利用できます。同じパターンを使用して、その過程で変数の一部またはすべてをバインドすることもできます。

マッチング

#

パターンは常に値に対してテストを行い、その値が期待どおりの形式であるかどうかを判断します。つまり、値がパターンと一致するかどうかを確認します。

一致を構成する内容は、使用しているパターンの種類によって異なります。たとえば、定数パターンは、値がパターンの定数と等しい場合に一致します。

dart
switch (number) {
  // Constant pattern matches if 1 == number.
  case 1:
    print('one');
}

多くのパターンでは、外側および内側パターンと呼ばれるサブパターンを使用します。パターンは、サブパターンに対して再帰的に一致します。たとえば、任意のコレクション型パターンの個々のフィールドは、変数パターンまたは定数パターンである可能性があります。

dart
const a = 'a';
const b = 'b';
switch (obj) {
  // List pattern [a, b] matches obj first if obj is a list with two fields,
  // then if its fields match the constant subpatterns 'a' and 'b'.
  case [a, b]:
    print('$a, $b');
}

一致した値の一部を無視するには、プレースホルダーとしてワイルドカードパターンを使用できます。リストパターンの場合は、残りの要素を使用できます。

構造分解

#

オブジェクトとパターンが一致すると、パターンはオブジェクトのデータにアクセスして、それを部分的に抽出できます。言い換えれば、パターンはオブジェクトを構造分解します。

dart
var numList = [1, 2, 3];
// List pattern [a, b, c] destructures the three elements from numList...
var [a, b, c] = numList;
// ...and assigns them to new variables.
print(a + b + c);

あらゆる種類のパターンを構造分解パターン内にネストできます。たとえば、この case パターンは、最初の要素が'a'または'b'である 2 つの要素のリストと一致し、構造分解します。

dart
switch (list) {
  case ['a' || 'b', var c]:
    print(c);
}

パターンが現れる場所

#

Dart 言語のいくつかの場所でパターンを使用できます。

このセクションでは、パターンによるマッチングと構造分解の一般的なユースケースについて説明します。

変数の宣言

#

Dart がローカル変数の宣言を許可する場所であれば、どこでもパターン変数宣言を使用できます。パターンは宣言の右側の値と一致します。一致すると、値を構造分解し、新しいローカル変数にバインドします。

dart
// Declares new variables a, b, and c.
var (a, [b, c]) = ('str', [1, 2]);

パターン変数宣言は、varまたはfinalのいずれかで始まり、その後にパターンが続いている必要があります。

変数の代入

#

変数代入パターンは代入の左側に配置されます。最初に、一致したオブジェクトを構造分解します。次に、新しい変数をバインドするのではなく、既存の変数に値を代入します。

変数代入パターンを使用して、3 つ目の一時変数を宣言せずに 2 つの変数の値を入れ替えます。

dart
var (a, b) = ('left', 'right');
(b, a) = (a, b); // Swap.
print('$a $b'); // Prints "right left".

switch 文と式

#

すべての case 句にはパターンが含まれています。これは、switch 文および、ならびにif-case 文に適用されます。case にはあらゆる種類のパターンを使用できます。

Case パターン反駁可能です。これらを使用すると、制御フローは次のいずれかを行うことができます。

  • 切り替えられているオブジェクトと一致して構造分解します。
  • オブジェクトが一致しない場合は実行を続行します。

case でパターンが構造分解する値はローカル変数になります。それらのスコープは、その case の本体内のみです。

dart
switch (obj) {
  // Matches if 1 == obj.
  case 1:
    print('one');

  // Matches if the value of obj is between the
  // constant values of 'first' and 'last'.
  case >= first && <= last:
    print('in range');

  // Matches if obj is a record with two fields,
  // then assigns the fields to 'a' and 'b'.
  case (var a, var b):
    print('a = $a, b = $b');

  default:
}

論理 OR パターンは、switch 式またはステートメントで複数の case が本体を共有する場合に便利です。

dart
var isPrimary = switch (color) {
  Color.red || Color.yellow || Color.blue => true,
  _ => false
};

switch 文では、複数の case が論理 OR パターンを使用せずに本体を共有できますが、ガードを共有するために複数の case を許可する場合に一意に役立ちます。

dart
switch (shape) {
  case Square(size: var s) || Circle(size: var s) when s > 0:
    print('Non-empty symmetric shape');
}

ガード句は、case の一部として任意の条件を評価します。条件が false の場合、switch を終了することはありません (case 本体で if ステートメントを使用した場合のように)。

dart
switch (pair) {
  case (int a, int b):
    if (a > b) print('First element greater');
  // If false, prints nothing and exits the switch.
  case (int a, int b) when a > b:
    // If false, prints nothing but proceeds to next case.
    print('First element greater');
  case (int a, int b):
    print('First element not greater');
}

for および for-in ループ

#

パターンをfor および for-in ループで使用して、コレクション内の値を反復処理および構造分解できます。

この例では、for-in ループでオブジェクト構造分解を使用して、<Map>.entries呼び出しが返すMapEntryオブジェクトを構造分解します。

dart
Map<String, int> hist = {
  'a': 23,
  'b': 100,
};

for (var MapEntry(key: key, value: count) in hist.entries) {
  print('$key occurred $count times');
}

オブジェクトパターンは、hist.entriesが名前付き型MapEntryを持っていることを確認し、名前付きフィールドサブパターンkeyvalueに再帰します。各反復でMapEntrykeyゲッターとvalueゲッターを呼び出し、結果をローカル変数keycountにそれぞれバインドします。

ゲッター呼び出しの結果を同じ名前の変数にバインドすることは一般的なユースケースであるため、オブジェクトパターンは変数サブパターンからゲッター名を推測することもできます。これにより、冗長なkey: keyのような変数パターンを:keyだけに簡略化できます。

dart
for (var MapEntry(:key, value: count) in hist.entries) {
  print('$key occurred $count times');
}

パターンのユースケース

#

前のセクションでは、パターンが他の Dart コード構成にどのように適合するかについて説明しました。2 つの変数の値を入れ替えたり、マップ内のキーと値のペアを構造分解したりするなど、興味深いユースケースを例として紹介しました。このセクションでは、さらに多くのユースケースについて説明し、次の質問に答えます。

  • パターンをいつ、なぜ使用するのか。
  • どのような問題を解決するのか。
  • どのイディオムに最適か。

複数の戻り値の構造分解

#

レコードを使用すると、単一の関数呼び出しから複数の値を集計して返すことができます。パターンを使用すると、関数呼び出しにインラインで、レコードのフィールドをローカル変数に直接構造分解する機能が追加されます。

次のように、各レコードフィールドに対して新しいローカル変数を個別に宣言する代わりに

dart
var info = userInfo(json);
var name = info.$1;
var age = info.$2;

関数が返すレコードのフィールドを、変数宣言または代入パターンと、そのサブパターンとしてレコードパターンを使用して、ローカル変数に構造分解できます。

dart
var (name, age) = userInfo(json);

パターンを使用して名前付きフィールドを持つレコードを構造分解するには

dart
final (:name, :age) =
    getData(); // For example, return (name: 'doug', age: 25);

クラスインスタンスの構造分解

#

オブジェクトパターンは、名前付きのオブジェクト型と照合し、オブジェクトのクラスが既に公開しているゲッターを使用してデータを構造分解できます。

クラスのインスタンスをデストラクトするには、名前付きの型に続けて、デストラクトするプロパティを括弧で囲んで指定します。

dart
final Foo myFoo = Foo(one: 'one', two: 2);
var Foo(:one, :two) = myFoo;
print('one $one, two $two');

代数的データ型

#

オブジェクトのデストラクトとswitch文は、代数的データ型のスタイルでコードを書くのに適しています。この方法は、以下の場合に使用します。

  • 関連する型のファミリーがある場合。
  • 各型に対して特定の動作が必要な操作がある場合。
  • その動作を、異なる型の定義全体に分散させるのではなく、1か所にまとめたい場合。

操作を各型のインスタンスメソッドとして実装する代わりに、サブタイプを切り替える単一の関数に操作のバリエーションを保持します。

dart
sealed class Shape {}

class Square implements Shape {
  final double length;
  Square(this.length);
}

class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

double calculateArea(Shape shape) => switch (shape) {
      Square(length: var l) => l * l,
      Circle(radius: var r) => math.pi * r * r
    };

受信 JSON の検証

#

Mapリストのパターンは、JSONデータ内のキーと値のペアをデストラクトするのに適しています。

dart
var json = {
  'user': ['Lily', 13]
};
var {'user': [name, age]} = json;

JSONデータが期待どおりの構造を持っていることがわかっている場合は、前の例は現実的です。しかし、データは通常、ネットワーク経由などの外部ソースから取得されます。その構造を確認するために、最初に検証する必要があります。

パターンを使用しない場合、検証は冗長になります。

dart
if (json is Map<String, Object?> &&
    json.length == 1 &&
    json.containsKey('user')) {
  var user = json['user'];
  if (user is List<Object> &&
      user.length == 2 &&
      user[0] is String &&
      user[1] is int) {
    var name = user[0] as String;
    var age = user[1] as int;
    print('User $name is $age years old.');
  }
}

単一のcaseパターンで同じ検証を実現できます。単一のcaseは、if-case文として最適に機能します。パターンは、より宣言的で、はるかに冗長でないJSON検証方法を提供します。

dart
if (json case {'user': [String name, int age]}) {
  print('User $name is $age years old.');
}

このcaseパターンは、同時に以下を検証します。

  • jsonは、処理を進めるために最初に外側のmapパターンと一致する必要があるため、マップです。
    • そして、マップであるため、jsonがnullでないことも確認します。
  • jsonにキーuserが含まれている。
  • キーuserは、2つの値のリストとペアになっています。
  • リストの値の型はStringintです。
  • 値を保持するための新しいローカル変数はnameageです。