Dart 3.0で導入されたPatternsの解説

# はじめに

Dart 3.0で **Patters**が導入されました。 Dart 3.0は、Flutter 3.10で採用されているので、Flutter 3.10から **Patterns**が使えます。

本記事では、この**Patterns**について解説します。


## 対象読者

* FlutterやDartの経験がある人で、Dart 3.0の新機能に興味ある人
    * FlutterやDartの基本については説明しません。


## 分離 （Destructuring)
分離をすることにより、オブジェクトから値を取り出すのが楽になります。

簡単な、Records型を返す関数(`userInfo`)を例に、どのように利用できるかを説明します。


ベースとなるコード、`userInfo` の返り値(`Records`型) を変数`info`に格納して、その後、それぞれのフィールドにアクセスしています。
```dart
// Recordsを返す関数
// name: １番目のポジションフィールド。 String
// age: 名前付きフィールド(age)。 int
(String name, {int age}) userInfo() {
  return ('John', age: 23);
}

void main() {
  // 変数infoで受け取って、
  final info = userInfo();
  // それぞれの要素にアクセスする。
  print(info.$1);  // ポジションフィールドは $1 でアクセス
  print(info.age); 
}
```

分離を使うことで、それぞれの要素に分離して取得することができます。

```dart
// Recordsを返す関数
(String name, {int age}) userInfo() {
  return ('John', age: 23);
}

void main() {
  // 分離して受け取る
  // １番目のポジションフィールドを String nameに入れる。
  // 名前付きフィールド(age)を int ageに入れる。
  final (String name, age: int age) = userInfo();
  print(name);
  print(age); 
}
```

型は省略できるので、次のように書くこともできます。
```dart
  final (String name, age: int age) = userInfo();
  // 次のように書くこともできる。
  final (name, age: age) = userInfo();
  print(name);
  print(age); 
```

名前付きフィールドの場合、フィールド名と変数名が同じ場合は次のようにもかける。
```dart
  final (String name, age: int age) = userInfo();
  // 次のように書くこともできる。
  final (name, :age) = userInfo();
  print(name);
  print(age); 
```


## 分離を使ったスワップ

分離を使うことで、値のスワップが簡単に記述できます。

```dart
void main() {
  var a = 'a';
  var b = 'b';
  (b, a) = (a, b);  // swap!
  print('$a $b'); // Prints "b a".
}
```



## 分離を使った、JSONの検証

分離を使うことで、JSONデータのパターンマッチによる検証と、値の取り出しが簡単に行なえます。

```dart
void main() {
  // 対象となるjsonデータ。
  // 一般的なJSONデータとするために、Map<String, dynamic>とする。
  final Map<String, dynamic> json = {
    'user': ['Lily', 13]
  };

  // JSONデータに対して、パターンマッチによる検証と、値の取り出しを行う。
  // * 'user'プロパティがあり、値は、[String, int]となるリスト型である。
  // * 値[String, int]をname, age として取り出す。
  var {'user': [String name, int age]} = json;
  
  print('$name, $age');  // Print 'Lily, 13'
}
```


もしも、分離が使えないと、次のような処理が必要となります。 Pattersの便利さが一目瞭然です。

```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.');
  }
}
```


検証が失敗すると、例外が発生します。

```dart
void main() {
  // 対象となるjsonデータ。
  // 一般的なJSONデータとするために、Map<String, dynamic>とする。
  final Map<String, dynamic> json = {
    'user': ['Lily', 13, 'tokyo']  // ３番目の値を追加！
  };

  var {'user': [String name, int age]} = json;  // 例外が発生！！
  print('$name, $age');  // 例外発生するので呼ばれない
}
```


try catchで例外処理をしても良いですが、[case pattern](https://dart.dev/language/patterns#switch-statements-and-expressions) を利用するとシンプルに記述できます。

```dart
void main() {
    // jsonデータ.
  final Map<String, dynamic> json = {
    'user': ['Lily', 13, 'tokyo'],
  };

  // {'user': [String name, int age]} にマッチするかのチェック
  if (json case {'user': [String name, int age]}) {
    print('$name, $age');  
  } else{
    // マッチしない場合
    print('unknown json');
  }
}
```

複数のJSONパターンとのマッチを行う場合は、`switch`を使うことができます。

```dart
void main() {
  // jsonデータ.
  final Map<String, dynamic> json = {
    'user': ['Lily', 13],   // 1番目のcaseにマッチ
    //'user': ['Lily', 13, 'tokyo'],  // 2番目のcaseにマッチ
  };
 
  switch (json) {
    // { 'user': ['Lily', 13] } にマッチ
    case {'user': [String name, int age]}:  
      print('$name, $age');  
    // { 'user': ['Lily', 13, 'tokyo'] } にマッチ 
    case {'user': [String name, int age, String addr]}:
      print('$name, $age, $addr');  
    default:
      print('unknown json');   
  }
}
```




## Pattern types

いろいろな種類のパターンについて説明します。


### List

Listの場合、List全体がマッチしないといけません。

```dart
var [a1, b1] = [1, 2];  // OK
var [a2, b2, c2] = [1, 2]; // NG
var [a3, b3] = [1, 2, 3]; // NG
```

`rest element (...)`を使うと、任意の数の要素にマッチすることできます。 1 pattern中で複数の `rest element`を記述することはできません。

```dart
// 先頭２つの要素をa,bに、末尾２つの要素をc,dに代入する。 残りは ... でマッチする。
var [a, b, ..., c, d] = [1, 2, 3, 4, 5, 6, 7];
// Prints "1 2 6 7".
print('$a $b $c $d');
```


`...rest` で `rest element`を取得することもできます。

```dart
// 残りの要素をrestに代入する。
var [a, b, ...rest, c, d] = [1, 2, 3, 4, 5, 6, 7];
// rest = [3, 4, 5] となる
print('$a $b $rest $c $d'); // Prints "1 2 [3, 4, 5] 6 7".
```


### Map

`List`とは違い、 `Map`は`Map`全体がマッチする必要はありません。
 余分な要素があってもマッチします。 

```dart
void main() {
  final dynamic data = {'foo': 1, 'bar': 2};
  
  // 'foo', 'bar' 両方あるので、当然マッチする。
  final {'foo': val1, 'bar': val2} = data;  // OK
  print('$val1, $val2'); // Print '1, 2'

  // 'foo'しかないけども、マッチする。  
  final {'foo': val3} = data;  // OK
  print('$val3'); // Print '1'
}
```



### Records

Records型は、`Map`とは違い、全体がマッチする必要があります。

```dart
void main() {
  // 名前付きプロパティ(myString)の値をfoo という変数に代入
  // 名前付きプロパティ(myNumber)の値をbar という変数に代入
  final (myString: foo, myNumber: bar) = (myString: 'string', myNumber: 1);
  print('$foo, $bar'); // Print 'string, 1'

  // 全体がマッチしないので、エラー
  final (myString: foo2) = (myString: 'string', myNumber: 1); // NG
}
```

プロパティ名と変数名が同じ場合は、プロパティ名を省略できます。

```dart
void main() {
  // 名前付きプロパティ(myString)の値をmyString という変数に代入。同じなので、プロパティを省略できる！
  // 名前付きプロパティ(myNumber)の値をmyNumber という変数に代入。同じなので、プロパティを省略できる。
  final (:myString, :myNumber) = (myString: 'string', myNumber: 1);
  print('$myString, $myNumber');  // Print 'string, 1'
}
```

### Wildcard

マッチさせるけども、変数に代入する必要がないときは、 `_` を使います。

```dart
void main() {
  var list = [1, 2, 3];
  var [_, two, _] = list;
  print('$two');   // Print 2
}
```


# TODO

* switch について書く