5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

はじめに

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に格納して、その後、それぞれのフィールドにアクセスしています。

// Recordsを返す関数
// name: 1番目のポジションフィールド。 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); 
}

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

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

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

型は省略できるので、次のように書くこともできます。

  final (String name, age: int age) = userInfo();
  // 次のように書くこともできる。
  final (name, age: age) = userInfo();
  print(name);
  print(age); 

名前付きフィールドの場合、フィールド名と変数名が同じ場合は次のようにもかける。

  final (String name, age: int age) = userInfo();
  // 次のように書くこともできる。
  final (name, :age) = userInfo();
  print(name);
  print(age); 

分離を使ったスワップ

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

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

分離を使った、JSONの検証

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

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の便利さが一目瞭然です。

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

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

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

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

try catchで例外処理をしても良いですが、case pattern を利用するとシンプルに記述できます。

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を使うことができます。

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全体がマッチしないといけません。

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を記述することはできません。

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

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

// 残りの要素を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とは違い、 MapMap全体がマッチする必要はありません。
余分な要素があってもマッチします。

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とは違い、全体がマッチする必要があります。

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
}

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

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

Wildcard

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

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

TODO

  • switch について書く
5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?