Open11

Dart / Pattern

Ukkey🐼Ukkey🐼

Petternsとは

  • "文(Statement)" や "式(Expression)" と同様に、Dart言語における構文カテゴリ
  • 実際の値とマッチする可能性のある値の集合の形を表す
  • 文脈やパターンの形によって以下どちらか、または両方を行う
    • 値にマッチする
    • 値を分解する
Ukkey🐼Ukkey🐼

値のマッチング

  • 特定の値に対して、期待する形をしているかどうかを検証する
  • 使用するPatternsの種類によってマッチ条件は異なる
// 例:定数パターンは、`1 == number` の場合にマッチする
switch (number) {
  case 1:
    print('one');
}
Ukkey🐼Ukkey🐼

サブパターン

パターンはそのサブパターンに再帰的にマッチする(どういうこと?)
例えば、コレクション型パターンの個々のフィールドは、変数パターンや定数パターンである可能性がある。(どういうこと?)

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');
}
Ukkey🐼Ukkey🐼

値の分解

  • オブジェクトとパターンがマッチすると、パターンはオブジェクトのデータにアクセスし、それを部分的に取り出すことができる(オブジェクトの分解)
var numList = [1, 2, 3];
// numListから3つの要素を分解し、a, b, cという名前の新しい変数として宣言する
var [a, b, c] = numList;
print(a + b + c);

分解するパターンの中には、どんな種類のパターンでも入れ子にすることができる。
例えば、このケースパターンは最初の要素が 'a' または 'b' である2要素リストにマッチし分解する。

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

Patternsを使用できる場所

  • ローカル変数の宣言と代入
  • for / for-in
  • if-case / swich-case
  • コレクションリテラルの制御フロー
Ukkey🐼Ukkey🐼

パターン変数の宣言

  • ローカル変数の宣言が可能な場所であればどこでも使用可能
  • パターンは右側の値とマッチする
  • マッチした場合、その値を分解して新しいローカル変数に割り当てる
// 新しい変数a、b、cを宣言する
var (a, [b, c]) = ('str', [1, 2]);

パターン変数の宣言は、varかfinalで始まり、その後にパターンが続かなければならない。

Ukkey🐼Ukkey🐼

パターン変数への代入

  • 変数代入パターンは代入の左側に当たる。
  • まず、マッチしたオブジェクトを分解し、次に新しい変数をバインドするのではなく、既存の変数に値を代入する

変数代入パターンを使って、3つ目の一時的な変数を宣言s売ることなく、2つの変数の値を入れ変える

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

パターン変数の宣言と代入は、ローカル変数に対してだけなのか?についての検証を行った

まず変数宣言についての検証。
適当にDataクラスを定義して、フィールドとしてパターン変数の宣言を記述してみた。

class Data {
  final (a, b) = ('a', 'b');
}

A pattern variable declaration may not appear outside a function or method.Try declaring ordinary variables and assigning from within a function or method.
訳:パターン変数の宣言は、関数やメソッドの外には出てきません。普通の変数を宣言して、関数やメソッドの中から代入してみてください。

上記のコンパイルエラーが発生。宣言はローカル変数でないとNGのようだ

次に代入はどうだろう。
先ほどと同様にDataクラスを定義し、既存の変数a,bに対し、メソッドから代入を行うように記述してみた。

class Data {
  var a;
  var b;

  void method() {
    (a, b) = ('a', 'b');
  }
}

Only local variables can be assigned in pattern assignments.Try assigning to a local variable.
訳: パターン・アサインで代入できるのはローカル変数だけです。ローカル変数に代入してみてください。

こちらも同様、コンパイルエラーが発生した。

以上の結果から、パターンを使用した変数の宣言・代入はいずれもローカル変数に対してのみ有効ということがわかった

Ukkey🐼Ukkey🐼

Case節

  • すべての case 節(switch文/式,if-case文)ではパターンが使用できる。
  • case節でのパターンは反駁可能(Refutable)であるため、マッチしない場合でもエラーをスローしない
  • パターンがcase節で分解した値は、そのcaseブロック内をスコープとするローカル変数となる。
void main() {
  const first = 0;
  const last = 10;

  dynamic value = 5;

  switch (value) {
    case 1:
      print('one');
    case >= first && <= last:
      print('in range');
    case (var a, var b):
      print('a = $a, b = $b');
    default:
      print('default');
  }
}

if-caseの場合

void main() {
  const first = 0;
  const last = 10;

  dynamic value = 5;

  if (value case 1) {
    print('one');
  } else if (value case >= first && <= last) {
    print('in range');
  } else if (value case (var a, var b)) {
    print('a = $a, b = $b');
  } else {
    print('default');
  }
}
Ukkey🐼Ukkey🐼

for / for-in ループ

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

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

object petternの変数名は省略可能

for (var MapEntry(:key, value: count) in hist.entries) {
  print('$key occurred $count times');
}
  List<(String, int)> hist = [
    ('a', 23),
    ('b', 100),
  ];

  for (final (key, count) in hist) {
    print('$key occurred $count times');
  }