改定
本記事投稿後にinline classがextension typeに、data classがvalue classに改名し、リリースバージョンもかなりずれ込んだので改定しました。
サマリ
Dart 3.0でRecordを導入したのに続き、extension type (inline class)、value class (data class)を導入する予定です。
また、value classの最大の(?)特徴であるプライマリコンストラクタをclass全般に導入する構想もあります。
value classのもう一つの特徴であるcopyWith()メソッドも、同じく導入予定の汎用機能であるマクロ(static meta-programming)を用いて実装するようですので、Recordにも導入されそうです。
そうなると、Recordのextension typeでもvalue classとほぼ同等のことが出来ます。
これらの特徴を下記の表に整理しました。
型機能 | Record | extension type | value class | extension type of Record |
---|---|---|---|---|
イミュータブル | ○ | ○ | ○ | ○ |
バリュー型 | ○ | −⁴ | ○ | ○⁵ |
アンボックス | ○ | ◎⁴ | ○ | ○⁵ |
実行時型名 | × | ×⁴ | ○ | ×⁵ |
実行時型 | ○ | ×⁴ | ○ | ○⁵ |
静的型名 | × | ○ | ○ | ○ |
静的型 | ○ | ○ | ○ | ○ |
プライマリ コンストラクタ¹ |
○ | ⇨ ○ | ○ | ⇨ △⁶ |
copyWith() ² |
⇨ ○³ | × | ○ | ⇨ △⁷ |
複数フィールド | ○ | × | ○ | ○ |
ステータス | 導入済(3.0) | 実装中(3.3?) | 仕様策定中(3.4?) | 3.3? |
1: value class向けに考案されたが、クラス全般に拡大適用する構想有り。(仕様策定中: 3.4?)
先行導入されたRecordのコンストラクタと本質的に同じ。
2: マクロ(static meta-programing)(仕様策定中: 3.4以降?)を用いて導入。
3: data classにcopyWithが導入される際に、Recordに導入されない理由は見当たらない。
4: コンパイラが常にアンボックス。(他は最適化が状況に応じてアンボックス)
従って、extension typeに実行時オブジェクトは無く、実行時型も実行時型名も無い。
振る舞いはフィールドのそれに従う。
5: 実行時はフィールドのRecord型が露出する。
6: extension typeとRecordのプライマリコンストラクタを併用して記述可能。
7: extension typeのプライマリコンストラクタとRecordのcopyWithを併用して記述可能。
このあと、それぞれ少し詳しく説明するとともに、関連するプライマリコンストラクタ、copyWith()
メソッド、マクロについても説明します。
詳細
Record (導入済: 3.0)
Recordはアンボックス最適化を期待したイミュータブルなバリュー型です。
SwiftのTupleに相当する機能です。
int
、double
、String
もリファレンス型のDartにおいて、初となるバリュー型です。
なお、全ての型がリファレンス型であるDartにおいて、バリュー型は不変のアイデンティティ(=リファレンス)を持たない(不定のアイデンティティしか持たない)ことを意味します。
詳細はDartのRecordは多重人格 - Qiitaを参照ください。
Recordのもう一つの特徴は独立したクラス名を持たないことで、同じ構造を持つものどうしは型チェックで弾かれませんが、これもDartでは初です。
また、関数パラメタ定義の文法を踏襲し、フィールドを含むクラス定義を兼ねたコンストラクタ定義があります。
これは、本質的には後述のプライマリコンストラクタです。
(int a, {int b, int c}) r = (1, b: 2, c: 3); // (1, 2, 3)
hashCode
ゲッター、==
オペレータ、toString()
等も自動的に専用品でオーバライドされます。
仕様: Records Feature Specification
extension type (実装中: 3.3?)
extension typeは既存クラスの内部表現を借りた別クラスを定義するものです。
Kotlinのvalue class (inline class)に相当するものです。
extension typeは完全に静的な型であり、唯一のstaticフィールドのラッパーオブジェクトを持たないという意味で、常にアンボックスされます。
これは、Recordと同様に実行時オーバーヘッドを抑えることを最大の目的としています。
typedef
が単なる別名であって静的にも独立した型を持たず、IDEのサポートやコンパイラの型チェックを受けられないのに対し、extension typeにはその代替としての役割も持ちそうです。
実行時の型を持たない完全に静的なクラスは、Dartにおいて初です。
なお、dynamic
型の変数等を介した実行時の振る舞いは剥き出しのフィールドの型のそれになります。
既にDartPadのmaster channelで動作し、中の人もそう言っているので、Dart 3.1で導入するのはほぼ確実と思ったのですが、Dart 3.3になりそうです。
仕様: Inline Classes
value class (Struct) (仕様策定中: 3.4以降?)
value classはアンボックス最適化を期待したイミュータブルなバリュー型です。
Kotlinのdata classに相当するものです。
DartではRecordに次いで2つ目のバリュー型になる予定です。
定義キーワードはKotlinと同じdata class
になるかstruct
かと思ったのですが、value class
になりそうです。
hashCode
ゲッター、==
オペレータ、toString()
等も自動的に専用品でオーバライドされます。
Recordとの違いは独自のクラス名を持つことで、同じ構造をつものどうしでも型チェックで弾かれます。
仕様: Value classes
プライマリコンストラクタ (仕様策定中: 3.4?)
value classおいて実行性能もさることながら、ユーザにとって最もインパクトのあるのはプライマリコンストラクタの簡潔な構文でしょう。
プライマリコンストラクタはKotlinにおける同名の機能に相当するものです。
関数パラメタ定義の文法を踏襲し、フィールドを含むクラス定義を兼ねたコンストラクタ定義です。
value class C(int a, {int b, int c = 3});
これは下記と同等です。
value class C {
C(this.a, {this.b, this.c = 3});
int a;
int b;
int c;
}
キーワードvalue class
に続く識別子はクラス名ではなく、コンストラクタ名です。
名前付きコンストラクタ形式も利用できるので、ライブラリプライベートにも出来ます。
value class C._(int a, {int b, int c = 3});
なお、プライマリコンストラクタはクラス全般に拡大適用する構想が有ります。
現在は仕様策定中ですが、コンパイラにおけるフロントエンドで実装できる構文糖衣なので、実装は比較的容易で、おそらくDart 3.1で導入されると思ったのですが、Dart 3.3で導入する動きもありません。
仕様: Fields and primary constructors
copyWith()
value classのもう一つの特徴は自動生成されるcopyWith()
メソッドでしょう。
copyWith()
メソッドはKotlinのdata classにおけるcopy()
メソッドに相当するものです。
このcopyWith()
はマクロによって自動生成されるようです。
マクロが汎用機能であることを考えると、copyWith()
がRecordに導入されない理由は見当たりません。
仕様: copyWith
マクロ (static meta-programming) (仕様策定中: 3.4以降?)
マクロの位置付けは、パッケージで実現していたコードジェネレータ機能の言語本体への取り込みです。
そのコードジェネレータの位置付けは、静的型付言語(Dart 2以降)におけるリフレクション機能(Dart 1以前)の代替と言えます。
toJson()
、fromJson()
の自動生成、クラスのイミュータブル化等が代表的な用途です。
現在仕様策定中ですが、コード生成だけあってその影響範囲は広く、慎重な検討が続けられるようですので、Dart 3.2以降の導入になるでしょう。
仕様: Macros
Recordのextension type
独立した型機能ではありませんが、value classとの比較のために説明します。
data class同様、静的型名を持つアンボックス最適化を期待したイミュータブルなバリュー型になります。
extension typeにおけるフィールド名f
がいかにも目障りですが、プライマリコンストラクタも比較的簡潔な記述で利用できます。
extension type C((int a, {int b, int c}) f);
var c = C((1, a: 2, b: 3));
仮にRecordがcopyWith()
を導入するなら、Recordのextension typesでも比較的簡潔な記述でcopyWith()
が利用できます。
var c2 = C(c.f.copyWith(b: 20));
print(c2.f) // (1, 20, 3)
そうなると、value classとの決定的な違いは実行時型名を持たないことだけです。
copyWith()
が無いvalue classはいかにもインパクトに欠けるので、value classはDart 3.4以降でマクロと同時に導入されるのではないでしょうか。
もっとも、そのときにはRecordでもcopyWith()
が使えるようになっていると思いますが、Recordのvalue typeと比較してスッキリした構文がメリットになるでしょう。