🪢
Dart で Union 型やろうぜ(実験的に)
Union 型とは
たとえば、number
の中でも、特定の値だけに制限したいとか、複数の型を受け入れたい時につけるやつです。
type ErrorCode =
| 400
| 401
| 402
| 403
| 404
| 405;
const codeA: ErrorCode = 400; // not error
const codeB: ErrorCode = 50000000000; // error
type QueryParameterValue = string | number | boolean;
const valueA: ErrorCode = 'name'; // not error
const valueB: ErrorCode = ['name']; // error
Dart でやりたいぜ
たとえば、firebase_analytics にこんなコードがあります。
void _assertParameterTypesAreCorrect(Map<String, Object?>? parameters) =>
parameters?.forEach((key, value) {
assert(
value is String || value is num,
"'string' OR 'number' must be set as the value of the parameter: $key. $value found instead",
);
});
これは、Firebase Analytics のパラメータの値を制限するため、想定してない型が来たら assert エラーを出すようにする実装です。
Firebase Analytics のパラメータ使う部分全てで実行されるようになってます。
ですが、これは実行エラーなので、パラメータに String と num 以外を渡しても静的エラーが出ません。
Dart には Union 型がないく、String | num
みたいなことができないので、こういう対応をしています。
sealed class 使おうぜ
そこで登場するのが sealed のクラス。
これで、String
か num
しか受け取らない値を作ることができます。
sealed class FirebaseAnalyticsParameterValue {
const FirebaseAnalyticsParameterValue._(this.value);
// コンストラクタも用意すると親切
const factory FirebaseAnalyticsParameterValue.string(String value) =
FirebaseAnalyticsParameterValueString;
const factory FirebaseAnalyticsParameterValue.number(num value) =
FirebaseAnalyticsParameterValueNum;
final dynamic value;
}
class FirebaseAnalyticsParameterValueString
extends FirebaseAnalyticsParameterValue {
const FirebaseAnalyticsParameterValueString(String super.value) : super._();
String get value => super.value;
}
class FirebaseAnalyticsParameterValueNum
extends FirebaseAnalyticsParameterValue {
const FirebaseAnalyticsParameterValueNum(num super.value) : super._();
num get value => super.value;
}
実行例
typedef FirebaseAnalyticsParameters = Map<String, FirebaseAnalyticsParameterValue>;
class FirebaseAnalyticsRepository {
final _analytics = FirebaseAnalytics.instance;
Future<void> logEvent({
required String name,
FirebaseAnalyticsParameters? parameters,
}) async {
await _analytics.logEvent(
name: name,
parameters: parameters?.map(
(key, value) => MapEntry(key, value.value),
)
);
}
}
void main() async {
const repository = FirebaseAnalyticsRepository();
await repository.logEvent(
name: 'hoge_event',
parameters: {
'param_1': FirebaseAnalyticsParameterValue.string('aaa'),
'param_2': FirebaseAnalyticsParameterValue.number(111),
}
);
}
課題
- 実装がめんどい。
- 結局 class を経由するので、リテラルそのまま使える Union 型よりは取り回しが効かない。
さっき思いついたものを書いて、あんまり試してないので、改善点とか大歓迎です。
Discussion