dart-use-pattern-matching について
Dart 3+のパターンマッチングを扱うスキルです。データの検証や分解、網羅的な分岐処理を行うDart開発者向けです。
主な機能
- 01Map・Listパターンによる照合と分解
- 02Recordパターンによる分解
- 03sealedクラスの網羅的な照合
- 04関係・論理パターンと条件ガード
- 05switch式とswitch文の使い分け
活用シーン
- 01ネストしたJSONの検証と値の抽出
- 02関数が返すRecordの分解
- 03sealedクラスの状態を網羅的に分岐
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ dhruvanbhalara/ skills --skill dart-use-pattern-matchingインストール後、Claude Code で/dart-use-pattern-matchingを入力して読み込まれたか確認します。
詳細
目次
パターン選択の戦略
対象のデータ構造に応じて、異なるパターンマッチング戦略を適用します。
- JSON の検証と分解:Map パターンと List パターンを使い、ネストされた構造を検証しながら、値をローカル変数に一度に束縛します。
- 複数の戻り値:関数から Record を返し、Record パターンを使って、名前付きまたは位置指定の値をスコープ内の変数に直接分解します。
- 代数的データ型(ADT):
sealedクラス階層と switch 式の Object パターンを組み合わせ、コンパイル時の網羅性チェックを活用します。 - 数値範囲と論理チェック:switch ケース内で、関係パターン(
>=、<など)と論理積(&&)または論理和(||)演算子を組み合わせます。 - ガード節:静的パターンだけでは表現できない動的条件を検証するには、
whenキーワードを使います。
スイッチ式とスイッチ文の比較
実行時のコンテキストに応じて、適切な switch 構文を選択します。
スイッチ式
- 使用する場面:switch 構文の評価結果として、単一の値を返す必要がある場合です。
- 構文:
final result = switch (value) { pattern => expression, _ => fallback }; - 規則:すべてのケースは式に評価される必要があります(文やコードブロックは使用できません)。ケースは網羅的でなければならず、コンパイラーによってチェックされます。暗黙のフォールスルーはサポートされていません。
スイッチ文
- 使用する場面:switch 構文で副作用、文、または複雑なコードブロックを実行する必要がある場合です。
- 構文:
switch (value) { case pattern: // statements break; // Implicitly breaks, but can be added explicitly } - 規則:空のケースはフォールスルーします。空でないケースは暗黙的にフォールスルーしません。
基本的なパターン構文
Dart 3+ の標準的なパターン構文を理解し、適用します。
- 論理和(
||):パターンを組み合わせます。いずれかのパターンが成功すると一致します。両方の分岐で同じ変数のセットを定義する必要があります。 - 論理積(
&&):両方のパターンが成功すると一致します。各分岐で重複する変数を定義してはいけません。 - 関係パターン:比較演算子(例:
> 100、!= 0)を使って定数と照合します。 - キャスト(
as):分解中に型変換を指定します(型チェックに失敗すると例外がスローされます)。 - Null チェック(
?):null 値を除外し、null 非許容の基底型で変数を束縛します。 - Null アサーション(
!):値が null 非許容であることを表明します(null の場合は例外がスローされます)。 - List パターン:特定の長さの配列に一致します(例:
[var first, var second])。末尾のリスト要素を無視するには、rest 要素(...)を使います。 - Map パターン:キーと値の組に一致します(例:
{'id': var id})。明示的に照合されていないキーは無視されます。 - Object パターン:クラスのインスタンスに一致し、プロパティ値を取り出します(例:
User(:final name))。
ワークフロー:パターンマッチングロジックの設計
次のチェックリストに従って、明確なパターンマッチング構造を実装します。
- 構造を特定する:ソースが Enum、Sealed Class 階層、Record、または生の JSON のいずれであるかを確認します。
- switch 構造を選択する:値の代入にはスイッチ式、副作用にはスイッチ文を選択します。
- ケースの束縛を定義する:明示的な Map、List、または Object パターンを構成し、必要なフィールドをスコープ内の変数に取り出します。
- ガードを追加する:条件付きの境界チェックには
when式を追加します。 - 網羅性を検証する:sealed クラスまたは enum を使う場合は、網羅性チェックをコンパイラーに任せます。オープン型を使う場合は、ワイルドカード
_のフォールバックを含めます。 - コンパイラーの出力を検証する:
dart analyzeを実行し、未処理のケースや型の不一致がないことを確認します。
例
JSON の検証と解析
マップの分解代入を使用して、構造の検証と値の抽出を一度に行います。
void processPayload(Map<String, dynamic> payload) {
if (payload case {'status': 'success', 'data': [String title, int count]}) {
print('Product: $title, Inventory: $count');
} else {
print('Invalid payload structure received');
}
}
シールドクラスの網羅的なマッチング
シールドクラスを使用して、ドメインロジックの実行時にコンパイル時の網羅性検証を強制します。
sealed class NetworkState {}
class NetworkIdle extends NetworkState {}
class NetworkLoading extends NetworkState {}
class NetworkSuccess extends NetworkState {
final String response;
NetworkSuccess(this.response);
}
class NetworkFailure extends NetworkState {
final String error;
NetworkFailure(this.error);
}
// Compiler guarantees all sub-states are handled without requiring a default fallback
String getStatusMessage(NetworkState state) {
return switch (state) {
NetworkIdle() => 'Waiting to connect...',
NetworkLoading() => 'Loading data...',
NetworkSuccess(:final response) => 'Data loaded: $response',
NetworkFailure(:final error) => 'Failed: $error',
};
}
レコードの分解代入と入れ替え
// Destructuring a positional and named return record:
(String name, {int age}) getUser() => ('Alice', age: 30);
void main() {
final (name, age: userAge) = getUser();
print('$name is $userAge years old');
// Value swapping without temp variables:
var (x, y) = (1, 2);
(y, x) = (x, y);
}
コメント
Markdown 対応