メインコンテンツにスキップ

パターン

パターンは、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');
}

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

分割代入

#

オブジェクトとパターンがマッチすると、パターンはそのオブジェクトのデータにアクセスし、部分的に抽出できます。言い換えれば、パターンはオブジェクトを分割代入します。

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);

分割代入パターン内にあらゆる種類のパターンをネストできます。たとえば、このケースパターンは、最初の要素が'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ではあらゆる種類のパターンを使用できます。

ケースパターン可否証です。これらは、制御フローが次のいずれかを行えるようにします。

  • スイッチされているオブジェクトにマッチして分割代入する。
  • オブジェクトがマッチしない場合は実行を続行する。

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

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文では、論理ORパターンを使用せずに複数のcaseが本体を共有できますが、複数のcaseがガードを共有できるようにするためには依然として独自の有用性があります。

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

ガード句は、caseの一部として任意の条件を評価します。条件がfalseの場合(case本体でif文を使用すると発生するような場合)、switchを終了しません。

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およびlistパターンは、JSONから解析されたデータなどの、デシリアライズされたデータのキーと値のペアを分割代入するのに役立ちます。

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

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

パターンがない場合、検証は冗長になります。

dart
if (data is Map<String, Object?> &&
    data.length == 1 &&
    data.containsKey('user')) {
  var user = data['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.');
  }
}

単一のケースパターンで同じ検証を達成できます。単一のケースは、if-case文として使用するのが最適です。パターンは、JSONを検証するための、より宣言的で、はるかに冗長でない方法を提供します。

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

このケースパターンは、同時に次のことを検証します。

  • jsonはマップである。なぜなら、続行するにはまず外部のマップパターンにマッチする必要があるため。
    • そして、マップであるため、jsonがnullでないことも確認します。
  • jsonにはキーuserが含まれている。
  • キーuserは2つの値のリストとペアになっている。
  • リストの値の型はStringintである。
  • 値を保持する新しいローカル変数はnameageである。