Patternによるパターンマッチ

Dart言語バージョン3.0以上でないとPatternsは利用できません

一、Patternの主な役割

PatternはDartにおける独立した文法区分(文、式と同列)で、値の構造・形を記述する構文です。大きく2つの機能、マッチ(Match)デストラクチャリング(分解代入)を備えており、同時に活用可能です。

  1. マッチ Match:対象の値が期待する形に合致するか検証する処理
    • 指定した定数と一致するか
    • 特定の型に該当するか
    • 指定された構造(List/Map/クラスインスタンス/レコード)を持つか
    • 追加の条件を満たすか
  2. デストラクチャリング Destructure:マッチ成功後、値内部の要素やフィールドを一気に取り出し、変数に紐付ける機能

1. 定数マッチのサンプル

void main() {
	int number = 1;
	switch (number) {
	  case 1:
		print('one'); // 定数パターンで number == 1 を判定
	  default:
		print('その他の数値');
	}
}Code language: Dart (dart)

実行結果

one

2. 入れ子サブパターンによるマッチ

パターンはサブパターンをネスト可能で、階層的に内部構造を検証できます

void main() {
  const a = 'a';
  const b = 'b';
  var obj = [ 'a', 'b'];
  switch (obj) {
    case [a, b]:
      print('$a, $b');
    default:
      print('マッチしません');
  }
}Code language: Dart (dart)

実行結果

a, b

3. 基本的なデストラクチャリング例

var numList = [1, 2, 3];
// Listパターンで配列要素を分解し、新規変数に代入
var [x, y, z] = numList; 
print(x + y + z); // 6 x=1 y=2 z=3Code language: PHP (php)

4. 入れ子パターンによるマッチ+分解代入

void main() {
  var list = ['b', 99];
  switch (list) {
    // ORパターン:先頭要素がaまたはb、2番目の値を変数cに紐付け
    case ['a' || 'b', var c]:
      print(c); // 99
  }
}Code language: Dart (dart)

二、Patternのよく使われる記法例

1. ローカル変数宣言時

分解代入に利用し、複数の変数を一括作成できます

// レコードとListをネストしたパターンで一括分解
var (str, [n1, n2]) = ('text', [10, 20]);
print(str); //text
print(n1 + n2); //30Code language: Dart (dart)

2. 既存変数への再代入(一時変数なしで値の入れ替えが可能)

var (a, b) = ("左", "右");
(b, a) = (a, b); // 2つの変数の値を入れ替え
print('$a $b'); // 右 左Code language: Dart (dart)

3. switch /if-case による分岐処理(最も頻出、whenによる追加条件付き)

case内部にはすべて否定可能なパターンを記述でき、マッチした分岐だけ実行、不一致なら次のcaseへ移行します

複数型に対応した基礎的なcase

void testSwitch(dynamic obj) {
  switch (obj) {
    case 1:
      print('数値1');
    case >= 10 && <= 20:
      print('10~20の数値');
    case (var a, var b):
      print('レコード:$a,$b');
    default:
      print('該当する条件なし');
  }
}Code language: Dart (dart)

ORパターン:複数条件で同一処理を共有

enum Color { red, yellow, blue, green }
void main() {
  Color color = Color.red;
  bool isPrimary = switch (color) {
    Color.red || Color.yellow || Color.blue => true,
    _ => false,
  };
  print(isPrimary); // true
}
Code language: PHP (php)

whenによるガード句(パターンマッチ後に追加のフィルタ条件を設定)

sealed class Shape {}
class Square { final double size; Square(this.size); }
class Circle { final double size; Circle(this.size); }

void checkShape(Shape shape) {
  switch (shape) {
    // SquareとCircleをまとめてマッチし、size>0の条件を追加
    case Square(size: var s) || Circle(size: var s) when s > 0:
      print('有効な対称図形');
    default:
      print('無効な図形');
  }
}Code language: Dart (dart)

whenと通常のifの動作差

void compareWhen((int, int) pair) {
  switch (pair) {
    case (int a, int b):
      if (a > b) print('前の値が大きい');
      // ifの条件に合致しない場合:switch全体を抜け、後続caseは実行されない
    case (int a, int b) when a > b:
      print('【whenガード】前の値が大きい');
      // whenの条件に合致しない場合:現在のcaseだけスキップ、次のcaseの判定に進む
    case (int a, int b):
      print('前の値が小さい、または等しい');
  }
}

void main() {
  print("===== (5, 2) a > b のテスト =====");
  compareWhen((5, 2));

  print("\n===== (2, 5) a < b のテスト =====");
  compareWhen((2, 5));
}Code language: Dart (dart)

4. for /for-in ループ(MapEntryの分解が実用的)


void main() {
	Map<String, int> hist = {'a': 23, 'b': 100};
	
	// 完全な記述
	for (var MapEntry(key: key, value: count) in hist.entries) {
	  print('$key$count');
	}
	
	// 省略記法:key:key を :key と簡略化
	for (var MapEntry(:key, value: count) in hist.entries) {
	  print('$key$count');
	}
}Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
a:23
b:100
a:23
b:100
Code language: CSS (css)

三、Patternの実務的な活用シナリオ

1:関数の複数戻り値(レコードの分解代入)

従来の記法(手間がかかる)

(String, int) userInfo() => ("Lily", 13);
void main() {
  var info = userInfo();
  var name = info.$1;
  var age = info.$2;
}
Code language: Dart (dart)

パターンによる簡略分解

var (name, age) = userInfo();
// 名前付きフィールドのレコードを分解
final (:name, :age) = getData(); // 戻り値 (name: "Doug", age:25)Code language: Dart (dart)

具体的なサンプルコード


(String, int) userInfo() => ("Lily", 18);


({String name, int age}) getData() => (name: "Doug", age: 25);

void main() {
  {
    var (name, age) = userInfo();
    print("名前:$name、年齢:$age");
  }

  // 内部のname/ageは上記の変数と干渉しない
  {
    final (:name, :age) = getData();
    print("名前:$name、年齢:$age");
  }
}Code language: Dart (dart)

2:クラスインスタンスの分解(オブジェクトパターン)

class Foo {
  final String one;
  final int two;
  Foo({required this.one, required this.two});
}

void main() {
  final Foo myFoo = Foo(one: "test", two: 66);
  var Foo(:one, :two) = myFoo;
  print('$one $two'); //test 66
}Code language: Dart (dart)

3:代数的データ型(シールドクラス+switch分岐による計算)

関連するサブクラスを一括処理でき、分散したインスタンスメソッドを記述する必要がなくなります

import 'dart:math';

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) => pi * r * r,
};
Code language: Dart (dart)

実装例

import 'dart:math';

// 同一ファイル内でのみサブクラスを定義可能なシールド親クラス
sealed class Shape {}

// 正方形サブクラス
class Square implements Shape {
  final double length;
  Square(this.length);
}

// 円サブクラス
class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

// switch式+オブジェクトパターンで面積を一括計算
double calculateArea(Shape shape) => switch (shape) {
  // Squareにマッチ、lengthフィールドを変数lに取り出す
  Square(length: var l) => l * l,
  // Circleにマッチ、radiusフィールドを変数rに取り出す
  Circle(radius: var r) => pi * r * r,
};

void main() {
  // 図形インスタンス作成
  Shape square = Square(5);
  Shape circle = Circle(3);

  // 面積計算実行
  double squareArea = calculateArea(square);
  double circleArea = calculateArea(circle);

  print("正方形の面積:$squareArea");    // 25.0
  print("円の面積:${circleArea.toStringAsFixed(2)}"); // 28.27
}Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
正方形の面積:25.0
円の面積:28.27Code language: CSS (css)

4:JSONデータの検証と分解(多重if判定のコードを大幅簡略化)

(パターン不使用の場合)多重ネストした型判定が必要でコードが長くなる

void oldCheck(Map data) {
  if (data is Map<String, Object?> && data.containsKey('user')) {
    var user = data['user'];
    if (user is List && user.length == 2) {
      String name = user[0] as String;
      int age = user[1] as int;
      print('$name $age');
    }
  }
}Code language: Dart (dart)

if-caseパターン:構造検証・型検証・変数紐付けを1行で完了

void newCheck(Map data) {
  if (data case {'user': [String name, int age]}) {
    print('ユーザー $name、年齢 $age');
  }
}Code language: Dart (dart)

このパターンは以下の項目を一括検証します:

  1. dataがMap型かつnullでない
  2. 「user」というキーが存在する
  3. userに対応する値が要素数2の配列である
  4. 配列1番目がString型、2番目がint型である
  5. name、ageというローカル変数に自動で値を紐付け

実際の利用サンプル

void main() {
  // 1. 正常なJSON構造:マッチ成功
  final validJson = {
    'user': ['Lily', 13]
  };
  newCheck(validJson); // 出力:ユーザー Lily、年齢 13

  // 2. 配列内の型不一致:年齢が文字列のためマッチ失敗
  final wrongTypeJson = {
    'user': ['Tom', '16']
  };
  newCheck(wrongTypeJson); // 出力なし

  // 3. user配列の要素数不足:マッチ失敗(年齢データなし)
  final shortJson = {
    'user': ['Jack']
  };
  newCheck(shortJson); // 出力なし

  // 4. userキー自体が存在しない:マッチ失敗
  final noUserJson = {
    'info': ['Bob', 20]
  };
  newCheck(noUserJson); // 出力なし
}

// if-caseによるMap+Listパターンで構造・型検証と分解代入を一括実行
void newCheck(Map data) {
  if (data case {'user': [String name, int age]}) {
    print('ユーザー $name、年齢 $age');
  } else {
    print('データ形式が不正です');
  }
}Code language: PHP (php)

Patternによるパターンマッチ

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です