【入門編】Dartの「パターンマッチング」を用いた「再帰的な木構造」の走査と変換 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!Dartの奥深い世界へようこそ。
FlutterやDartでの開発に慣れてくると、「もっとエレガントにデータを処理したい」「複雑な条件分岐をもっとスッキリ書きたい」と感じる瞬間がやってきますよね。

今回は、Dart 3で導入された「パターンマッチング」と、プログラミングの定番でありながら難所と感じやすい「再帰的な木構造(ツリー構造)」の走査・変換をテーマに解説します。

ここをクリアすると、あなたのコードは劇的に美しく、そして堅牢になります。先輩エンジニアとして、優しく、かつ本質的な部分までしっかりと導いていきますね。それでは、一緒にDartの極限の世界を覗いてみましょう!

—

1. なぜ「パターンマッチング × 木構造」なのか?

プログラムを書いていると、ファイルシステム、UIのウィジェットツリー、JSONのパース結果など、「枝分かれしていくデータ構造(木構造)」に出会うことがたくさんあります。

従来のオブジェクト指向プログラニングでは、こうした木構造を扱うために「各ノードに共通のインターフェースを持たせ、ポリモーフィズム(多態性)で処理する」のが主流でした。もちろんそれも素晴らしいアプローチですが、「後から新しい種類の処理を追加したくなったとき、全てのクラスを書き換えないといけない」という弱点がありました。

そこで登場するのが、Dart 3のパターンマッチング(`switch` 式とパターン)です。
データ構造と処理を綺麗に分離し、まるで人間が目で見て「あ、ここはこういう構造だからこう処理しよう」と判断するように、直感的にコードを書くことができるんです。

—

2. 実装の舞台:表現力豊かな「木のデータ構造」を作る

まずは、今回扱う木構造を定義しましょう。
今回は、シンプルな「計算式の木(AST: 抽象構文木)」を考えてみます。例えば `(3 + 5) 2` のような数式ですね。

Dart 3の Sealedクラス(封印されたクラス) を使うと、コンパイラが「どのような種類のノードが存在するか」を完全に把握できるようになります。これがパターンマッチングと最高の相性を発揮します。

// 数式の木構造を表すSealedクラス
sealed class Expr {}

// 1. 数値の葉(これ以上枝分かれしない)
class NumberNode extends Expr {
final int value;
NumberNode(this.value);
}

// 2. 二項演算の枝(左右にさらに式を持つ)
class BinaryOpNode extends Expr {
final String operator; // ‘+’, ‘-‘, ”, ‘/’
final Expr left;
final Expr right;

BinaryOpNode(this.operator, this.left, this.right);
}

頭の中で、この構造をイメージ図にしてみましょう。

[ (BinaryOpNode) ]
/ \
[ + (BinaryOpNode) ] [ 2 (NumberNode) ]
/ \
[ 3 ] [ 5 ]
(NumberNode) (NumberNode)

綺麗に枝分かれしていますよね。このデータ構造に対して、再帰的に処理を行っていきます。

—

3. パターンマッチングを使った「走査(評価)」の魔法

それでは、この木構造を実際に計算(評価)する関数を作ってみましょう。
Dart 3の `switch` 式とオブジェクトパターンを使うと、驚くほどスッキリと書くことができます。

int evaluate(Expr expr) {
// switch自体が値を返す「式」として機能する(Dart 3の強力な機能!)
return switch (expr) {
// パターン1: NumberNodeだったら、そのまま中身の値を返す
NumberNode(:final value) => value,

// パターン2: BinaryOpNodeだったら、左右の枝を再帰的に評価して計算する
BinaryOpNode(:final operator, :final left, :final right) => switch (operator) {
‘+’ => evaluate(left) + evaluate(right),
‘-‘ => evaluate(left) – evaluate(right),
” => evaluate(left) evaluate(right),
‘/’ => (evaluate(left) / evaluate(right)).toInt(),
_ => throw FormatException(‘未知の演算子です: $operator’),
},
};
}

ここがポイント!

  • オブジェクトパターン (`NumberNode(:final value)`): クラスのプロパティ(`value`)を直接かつ簡潔に変数にバインド(抽出)しています。わざわざ `(expr as NumberNode).value` のような冗長なキャストを書く必要はもうありません。
  • 網羅性チェック(Exhaustiveness Checking): `Expr` が `sealed` クラスとして定義されているため、Dartのコンパイラは「すべてのサブクラス(`NumberNode`, `BinaryOpNode`)が網羅されているか」をコンパイル時にチェックします。もし新しいノードの種類を追加し忘れたら、コンパイルエラーで教えてくれます。これが非常に強力です!

実際に動かしてみましょう。

void main() {
// 式: (3 + 5) 2 を構築
final tree = BinaryOpNode(
”,
BinaryOpNode(‘+’, NumberNode(3), NumberNode(5)),
NumberNode(2),
);

final result = evaluate(tree);
print(‘計算結果: $result’); // 出力: 計算結果: 16
// ここをクリアすれば、Dartのパターンマッチングはバッチリマスターできますよ!
}

—

4. 発展編:木構造を「変換」してみる(オプティマイザ)

走査ができるようになったら、次は「変換」です。
例えば、「数式の木を受け取って、人間が読みやすい文字列(中置記法)に変換する関数」を作ってみましょう。これも全く同じパターンマッチングの応用で実現できます。

String formatExpr(Expr expr) {
return switch (expr) {
NumberNode(:final value) => ‘$value’,
BinaryOpNode(:final operator, :final left, :final right) =>
‘(${formatExpr(left)} $operator ${formatExpr(right)})’,
};
}

void main() {
final tree = BinaryOpNode(
”,
BinaryOpNode(‘+’, NumberNode(3), NumberNode(5)),
NumberNode(2),
);

print(‘数式表現: ${formatExpr(tree)}’);
// 出力: 数式表現: ((3 + 5) 2)
}

どうでしょうか? データ構造の形状に合わせてパターンを書き分けるだけで、自由自在に木を加工・変換できることが実感できたのではないでしょうか。

—

5. 陥りやすい罠と文法エラーの回避法

パターンマッチングを使い始めるときに、多くの開発者がハマりがちなポイントをいくつかシェアしておきますね。

① カッコのつけ忘れ・書き方の勘違い

オブジェクトパターンを使うときは、コロン `:` や変数名の位置に注意が必要です。

  • ❌ 誤り: `NumberNode(value)` (ただのコンストラクタマッチになってしまい、値を取り出せない)
  • ⭕️ 正しい: `NumberNode(:final value)` または `NumberNode(value: final v)`

② 網羅性(Exhaustiveness)エラーで怒られる

`switch` 式で `sealed` な階層を扱う際、すべてのパターンを網羅していないと、コンパイラが容赦なくエラーを吐きます。「一部のケースしか処理しない」という場合でも、必ずデフォルトケース `_ => …` を用意するか、すべてのサブクラスを列挙する必要があります。これはバグを未然に防ぐためのDartからの優しい贈り物だと受け取りましょう!

—

まとめ

今回は、Dart 3のパターンマッチングを使った「再帰的な木構造の走査と変換」について解説しました。

1. Sealedクラスでデータの種類を安全に定義する。
2. `switch` 式とオブジェクトパターンで、冗長なキャストなしに美しくデータを抽出する。
3. 再帰呼び出しで複雑なツリー構造をスッキリと解決する。

このパターンをマスターすれば、複雑なJSONのバリデーション、独自のDSL(ドメイン固有言語)のパーサー、Flutterのカスタムレイアウト計算など、どんな難問でもエレガントに解きほぐせるようになります。

ここをクリアしたあなたなら、もうDartの中級者への階段を確実に登っていますよ。ぜひ実際のプロジェクトでも使ってみてくださいね!

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