🌏

【Dart3.3】Extension typesについて

2024/02/20に公開
2

概要

https://dart.dev/language/extension-types
Extension typesはDart3.3から利用可能になった機能の一つの拡張型。
※以下公式より抜粋

拡張型は、表現型と呼ばれる、基になる型のオブジェクトで使用できる一連の操作 (またはインターフェイス) に規律を適用します。
拡張型のインターフェイスを定義するときは、表現型の一部のメンバーを再利用したり、他のメンバーを省略したり、他のメンバーを置き換えたり、新しい機能を追加したりすることを選択できます。

要は、ラップした表現型を拡張型として定義し、意味のある型にして表現することができるよというもの。
...言葉だけではわかりづらいかもしれないので実際に実装を確認していきます。

実装

宣言

/// [Hoge]という拡張型を定義
/// 表現型[int]をラップ
extension type Hoge(int count) {
  // ...
}

上記定義をすることにより

  • int get countのように、暗黙的なゲッターが定義される
    • 厳密にはfinalフィールドとして定義されている
  • Hoge(int count) : count = countのような、暗黙的なコンストラクターが定義される
    そのため、以下のような記述が可能となる
void main() {
  final hoge = Hoge(100);
  final castHoge = hoge as int;
  print(castHoge); // 100
}

コンストラクター

拡張メソッドとは違い、拡張型自体でコンストラクターを定義することが可能。
また、プライベートコンストラクターやファクトリーメソッドを定義することも可能

/// 複数のコンストラクターを定義できる
extension type Hoge(int count) {
  Hoge.i(this.count);

  Hoge.l(String length) : count = int.parse(length);
}

/// プライベートコンストラクターとして外からの参照を非表示にすることも可能
extension type const Fuga._(int count) {
  Fuga.i(this.count);

  /// factory methodを定義することも可能
  factory Fuga.normal() {
    return Fuga.i(100);
  }
}

メンバー

基本的なメンバーの宣言が可能

extension type Hoge(int count) {
  Hoge get myCount => this;

  bool isMyCount(int value) => count == value;
}

void main {
  final hoge = Hoge(100);
  print(hoge.myCount); // 100
  print(hoge.isMyCount(99)); // false
}

implements

以下のようなことが利用できる

  • 拡張タイプにサブタイプ関係を導入することが可能
  • 表現オブジェクトのメンバーを拡張型インターフェイスに追加可能
    要は、Hogeという拡張型に、表現型intのメンバーを利用することが可能となったり
    Dependency Inversion(依存性逆転の原則)の様な、設計をすることも可能となる。
extension type Hoge(int count) implements int { 
}

void main() {
  final hoge = Hoge(100);
  // [int]型のメンバー[isOdd]を使用することが可能となる
  print(hoge.isOdd); // false
}

使用例

こちらの記事で紹介したResult型をExtension typesを使って表現してみました。
https://zenn.dev/masakunn/articles/e9244ba264578e


class ApiRequest {
  Result<bool, Exception> request() {
    try {
      // send api request...
      return const Result.success(true);
    } on Exception catch (e) {
      return Result.failure(e);
    }
  }
}

/// Success / Failureをを返すfactory methodを定義する親拡張クラス
/// Resutl型自体はプライベートな拡張型として定義
extension type const Result<S, E extends Exception>._(
    ({S? value, E? exception}) _) {
  const factory Result.success(S value) = Success<S, E>;

  const factory Result.failure(E exception) = Failure<S, E>;
}

/// generic Sを返す拡張型Success
extension type const Success<S, E extends Exception>._(
    ({S? value, E? exception}) _) implements Result<S, E> {
  const Success(S value) : this._((value: value, exception: null));
}

/// generic Eを返す拡張型Failure
extension type const Failure<S, E extends Exception>._(
    ({S? value, E? exception}) _) implements Result<S, E> {
  const Failure(E exception) : this._((value: null, exception: exception));
}

やっていることは、sealedクラスで定義したResult型と何ら変わりわないです。
個人的には、Extension typesで定義したResult型の方が良いなと感じました。
理由としては以下があがりますが、好みは分かれると思いますので、Result型を検討している方は、参考にしていただけたら嬉しいです。

  • 拡張型Resultに各継承関係にある拡張型をfactory methodとして宣言的に定義 / 集約できる
  • Swiftに似たような呼び出しができて好き(Swiftの方が型省略ができるからもっと好きではある)

所感

Dart3.3Flutter3.19.0では他にも新機能やレンダリングの改善などさまざまな改修が入りましたが、
Extension typesが個人的には嬉しいポイントでしたので紹介させていただきました。
他にも使用例などありましたらコメントなどいただけたら嬉しいです。

Discussion

Cat-sushiCat-sushi
  • int get countのように、暗黙的なゲッターが定義される

間違いではないのですが、finalフィールドですね。

Hoge(int count) : count = countのような、暗黙的なコンストラクターが定義される
そのため、以下のような記述が可能となる

明示的なextension typeコンストラクタ(兼型(フィールドを含む)定義)です。

mama

コメントありがとうございます!

間違いではないのですが、finalフィールドですね。

おっしゃる通りですね!
公式の内容に惑わされていました。
finalフィールドでもありgetter property(厳密には違いますが)でもありますね。

An implicit getter for the representation object with the representation type as the return type: int get i.
An implicit constructor: E(int i) : i = i.

明示的なextension typeコンストラクタ(兼型(フィールドを含む)定義)です。

こちらは暗黙的にextension type内でconstructorが定義されているものかと思うのですが(厳密には表現宣言自体が暗黙的なコンストラクター)、
明示的に宣言もできる(private constructorに対して)ためその様な表現となる
という解釈で齟齬なさそうでしょうか?(私の見落としなどでしたらご指摘いただきましと幸いです。)