【入門編】Dartの「リストパターン」における可変長マッチングと、スプレッド演算子の微妙な関係 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!Dartの奥深い世界へようこそ。
Flutterでのアプリ開発やDart単体でのバックエンド開発、楽しんでいますか?

今回は、Dart 3で導入されてから私たちのコーディングスタイルを劇的に変えてくれた「パターンマッチング」、その中でも特に強力な「リストパターン」と「スプレッド演算子(`…`)」の切っても切れない関係について深掘りしていきましょう。

「リストの先頭だけ取り出したい」「真ん中の要素はどうやって無視するんだっけ?」といった疑問をスッキリ解消して、明日からのコードをよりスマートにしていきましょうね。ここをクリアすれば、Dartのデータ構造の扱いはもうバッチリマスターできますよ!

—

1. Dart 3のリストパターンとは?(基本のおさらい)

まずは、Dart 3で何ができるようになったのかを軽くおさらいしましょう。
従来のDartでは、リストの要素を取り出すにはインデックスを指定してアクセスする必要がありましたよね。

// 昔ながらの書き方
var numbers = [1, 2, 3];
var first = numbers[0];
var second = numbers[1];

これがDart 3のパターンマッチングを使うと、変数の宣言と同時に「リストの構造そのものにマッチさせて値をごっそり抽出」できるようになります。

// Dart 3のリストパターン
var numbers = [10, 20, 30];
var [a, b, c] = numbers;

print(a); // 10
print(b); // 20
print(c); // 30

まるでパズルのピースがカチッとはまるように、リストの形と変数の形を同期させることができるんです。これがすごく直感的で気持ちいいんですよね。

—

2. 可変長マッチングとスプレッド演算子 `…` の魔法

さて、ここからが本題です。
「要素の数が固定されていない(可変長な)リスト」を扱いたいときはどうすればいいでしょうか? 例えば、「先頭の1件だけ欲しいけど、残りの要素の数は変動する」といったケースです。

ここで登場するのが、おなじみのスプレッド演算子(`…`)です。リストパターンの中では、これが「残りの要素をまとめて受け止める(キャプチャする)ワイルドカード」として機能します。

図解的にイメージしてみましょう。

リスト: [ 1, 2, 3, 4, 5 ]
パターン: [ first, …rest ]
結果:
first = 1
rest = [ 2, 3, 4, 5 ]

実際のコードで書いてみますね。

void main() {
var scores = [95, 80, 75, 60];

// 先頭の1つを `top` に、残りを `others` という別リストに分ける
var [top, …others] = scores;

print(‘最高得点: $top’); // 最高得点: 95
print(‘その他のスコア: $others’); // その他のスコア: [80, 75, 60]
}

すごく便利ですよね! `…others` のように書くことで、何個要素が入っていようとも、余った分をすべて新しいリストとしてキャプチャしてくれます。

末尾や真ん中を抜くことも可能

このスプレッド演算子は、先頭だけでなく末尾や真ん中にも配置できます。

void main() {
// 末尾を取り出す
var path = [‘home’, ‘user’, ‘documents’, ‘file.txt’];
var […dirs, fileName] = path;

print(dirs); // [home, user, documents]
print(fileName); // file.txt
}

—

3. 陥りやすい文法エラー:スプレッド演算子の「致命的なルール」

さて、ここからが一番重要なポイントです。
可変長のリストパターンを書いていると、ついやってしまいがちな「コンパイルエラー(文法違反)」があります。

次のコードを見て、どこがいけないか分かりますか?

// ⚠️ 良い子は真似してはいけない例
var [first, …middle, last] = [1, 2, 3, 4, 5];

「先頭と、末尾と、その間の残りすべてを取り出したいんだな」と人間の頭では直感的に理解できますが、Dartのコンパイラはこのコードをスパッと跳ね返します。

「Bad state: A list pattern can only contain one spread element.」(リストパターンに含められるスプレッド演算子は1つだけです)というエラーになります。

なぜ複数のスプレッドが許されないのか?(言語の裏側)

なぜDartは複数のスプレッドを許さないのでしょうか?
ちょっと意地悪なケースを考えてみてください。

もし `[ …left, …right ]` のようなパターンが許されるとしたら、`[1, 2, 3, 4, 5]` というリストをマッチさせるとき、どこで `left` と `right` を区切ればいいのでしょうか?

  • `left` = `[1]` で、`right` = `[2, 3, 4, 5]` なのか?
  • それとも `left` = `[1, 2, 3]` で、`right` = `[4, 5]` なのか?

コンピュータ(コンパイラ)から見ると、区切り位置が無限に存在してしまい、一意に決定できない(曖昧すぎる)のです。
そのため、Dartの言語仕様として「1つのリストパターンに使えるスプレッド演算子は最大1つまで」という厳格なルールが敷かれています。

—

4. 実戦で役立つ!スプレッドパターンを巧みに使うテクニック

「じゃあ、真ん中の要素を取り出したいときはどうすればいいの?」と思いますよね。
そんなときは、スプレッド演算子を名無しのプレースホルダー(アンダースコア `_`)と組み合わせるか、段階的にパターンを適用するのが定石です。

例えば、「最初と最後以外の、真ん中のデータだけをごっそり抜き出したい」場合のイディオムを見てみましょう。

void main() {
var logEntries = [‘INFO’, ‘Connection established’, ‘User authenticated’, ‘DEBUG’];

// 先頭と末尾を捨てつつ、真ん中をレストパターンで受け取る
var [_, …middle, _] = logEntries;

print(middle); // [Connection established, User authenticated]
}

これならスプレッドは `…middle` の1つだけなので、コンパイルも綺麗に通ります!
`_`(アンダースコア)は「値は要らないけれど、そこにあることだけをマッチさせたい」というときに使える無名の変数(ワイルドカード)です。このテクニックは現場で非常によく使いますので、ぜひ覚えておいてくださいね。

—

まとめ

今回は、Dartのリストパターンにおける可変長マッチングとスプレッド演算子の関係について解説しました。

  • リストパターンを使うと、データの構造をそのまま変数にバインドできる。
  • スプレッド演算子(`…`)を使うことで、残りの要素をまとめて可変長リストとしてキャプチャできる。
  • ただし、1つのリストパターンに使えるスプレッドは「1つだけ」という鉄則がある(曖昧さを排除するため)。
  • 真ん中を取り出したいときは、不要な部分を `_`(アンダースコア)でマスクしつつ、スプレッドを1つだけ使うとスマートに書ける。

ここをマスターしておくと、JSONのパース処理や、ルーティングのパス解析、複雑なデータ構造のハンドリングが驚くほどエレガントになります。

ぜひご自身のコードでも試してみてくださいね。それでは、また次回の記事でお会いしましょう!バッチリ使いこなしていきましょう!

タイトルとURLをコピーしました