はじめに
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');
...rest
で rest 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
とは違い、 Map
はMap
全体がマッチする必要はありません。
余分な要素があってもマッチします。
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 について書く