[読書会]freezed 2.5.7 翻訳及び補足(How to use / Union types)

freezedは、データクラス、ユニオン(合併型)、およびクローニング用のコードジェネレーターです。
これはDartプログラミング言語におけるモデル定義を簡単にするために設計されています。

Note(注釈)
現在、Freezedのマクロを使用した初期プレビュー版が利用可能です。
詳細については、GitHubのリンク https://github.com/rrousselGit/freezed/tree/macros を参照してください。

本ブログの(上記https://pub.dev/packages/freezedの)翻訳および解説の目次頁(構成上のトップページ)は次のURLになります。

また前回記事は、こちらになります。

本頁は、上記(目次)のうちの、「How to use / Union types」の章の翻訳および解説頁になります。

2.5 Union types

他の言語から来た方は、「ユニオン型」、「シールされたクラス」、パターン マッチングなどの機能に慣れているかもしれません。
これらは型システムと組み合わせると強力なツールになりますが、Dart 2 ではサポートされていません。Dart 3 ではサポートされていますが、特に使いやすくはありません。
しかし、心配はいりません。Freezed はこれらをサポートしており、役立つユーティリティをいくつか生成します。簡単に言うと、Freezed クラスでは複数のコンストラクタを記述できます。

Dart
@freezed
sealed class Union with _$Union {
  const factory Union.data(int value) = Data;
  const factory Union.loading() = Loading;
  const factory Union.error([String? message]) = Error;
}

NOTE)
この例では、Dart 3 で導入された seal キーワードを使用しています。Dart 3 を使用している場合は、Freezed ユニオンを定義するときに常に seal キーワードを使用する必要があります。まだ Dart 2 を使用している場合はこれを無視しても問題ありませんが、ネイティブ パターン マッチングのために Dart 3 にアップグレードすることをお勧めします。

これを行うことで、モデルは異なる相互排他的な状態になることができます。 特に、このスニペットはモデル Union を定義しており、そのモデルには 3 つの可能な状態があります。

  • data
  • loading
  • error

定義したファクトリ コンストラクタの右手に意味のある名前をどの様に付けたかに注目して下さい。これらは後で役に立ちます。

また、この例では、次のようなコードを記述できなくなっていることに気づくかもしれません。

(この記述はできません):
void main() {
  Union union = Union.data(42);

  print(union.value); // compilation error: property value does not exist
}

その理由については次のセクションで説明します。

(1) Shared properties

共有プロパティ

複数のコンストラクターを定義すると、すべてのコンストラクターに共通ではないプロパティを読み取ることができなくなります。 たとえば、次のように書くとします。

Dart
@freezed
sealed class Example with _$Example {
  const factory Example.person(String name, int age) = Person;
  const factory Example.city(String name, int population) = City;
}

そうなると、年齢人口を直接読み取ることができなくなります

そうなると、年齢と人口を直接読み取ることができなくなります。
var example = Example.person('Remi', 24);
print(example.age); //コンパイルエラー!

一方、すべてのコンストラクターで定義されているプロパティを読み取ることができます。たとえば、name 変数は Example.person と Example.city の両方のコンストラクターに共通です。 したがって、次のように書くことができます。

Dart
var example = Example.person('Remi', 24);
print(example.name); // Remi
example = Example.city('London', 8900000);
print(example.name); // London

同じロジックを copyWith にも適用できます。 すべてのコンストラクターで定義されたプロパティで copyWith を使用できます。

Dart
var example = Example.person('Remi', 24);
print(example.copyWith(name: 'Dash')); // Example.person(name: Dash, age: 24)

example = Example.city('London', 8900000);
print(example.copyWith(name: 'Paris')); // Example.city(name: Paris, population: 8900000)

一方、特定のコンストラクターに固有のプロパティは利用できません。

Dart
var example = Example.person('Remi', 24);

example.copyWith(age: 42); // compilation error, parameter `age` does not exist

この問題を解決するには、いわゆる「パターン マッチング」を使用してオブジェクトの状態をチェックする必要があります。

(2) Using pattern matching to read non-shared properties

パターンマッチングを使用して非共有プロパティを読み取る

このセクションでは、次の結合について考えてみましょう。

Dart
@freezed
sealed class Example with _$Example {
  const factory Example.person(String name, int age) = Person;
  const factory Example.city(String name, int population) = City;
}

パターン マッチングを使用して Example インスタンスのコンテンツを読み取る方法を見てみましょう。 このためには、スイッチを使用した Dart 3 の組み込みパターン マッチングを使用する必要があります。

Dart
switch (example) {
  Person(:final name) => print('Person $name'),
  City(:final population) => print('City ($population)'),
}

Dart 2を使用している場合は、Freezedによって生成された従来のパターンマッチングユーティリティを使用して、オブジェクトのコンテンツを検査することもできます。

(2-1) (Legacy) Pattern matching utilities

(レガシー) パターン マッチング ユーティリティ

警告)
Dart 3では、Dartにはシールクラスを使用したパターンマッチングが組み込まれています。そのため、パターンマッチングにFreezedの生成されたメソッドに頼る必要はなくなりました。

(2-1-1) When

when メソッドは、破壊を伴うパターン マッチングと同等です。
メソッドのプロトタイプは、定義されたコンストラクターによって異なります。

例えば、with の場合:

Dart
@freezed
sealed class Union with _$Union {
  const factory Union(int value) = Data;
  const factory Union.loading() = Loading;
  const factory Union.error([String? message]) = ErrorDetails;
}

そして、when ではこうなります:

Dart
var union = Union(42);

print(
  union.when(
    (int value) => 'Data $value',
    loading: () => 'loading',
    error: (String? message) => 'Error: $message',
  ),
); // Data 42

一方、もしこの様に定義すると:

Dart
@freezed
sealed class Model with _$Model {
  factory Model.first(String a) = First;
  factory Model.second(int b, bool c) = Second;
}

when ではこうなります:

Dart
var model = Model.first('42');

print(
  model.when(
    first: (String a) => 'first $a',
    second: (int b, bool c) => 'second $b $c'
  ),
); // first 42

各コールバックがコンストラクタの名前およびプロトタイプとどのように一致するかに注目してください。

(2-1-2) Map

map メソッドは when と同等ですが、構造化はありません。
次のクラスを考えてみましょう。

Dart
@freezed
sealed class Model with _$Model {
  factory Model.first(String a) = First;
  factory Model.second(int b, bool c) = Second;
}

この様なクラスの場合、when だとこうなります:

Dart
var model = Model.first('42');

print(
  model.when(
    first: (String a) => 'first $a',
    second: (int b, bool c) => 'second $b $c'
  ),
); // first 42

map だとこうなります:

Dart
var model = Model.first('42');

print(
  model.map(
    first: (First value) => 'first ${value.a}',
    second: (Second value) => 'second ${value.b} ${value.c}'
  ),
); // first 42

これは、たとえば copyWith/toString などの複雑な操作を実行する場合に便利です。

(続きの記事はこちら)

コメントを残す