I'm migrating a code base to null safety, and it includes lots of code like this:
MyType convert(OtherType value) {
return MyType(
field1: value.field1,
field2: value.field2 != null ? MyWrapper(value.field2) : null,
);
}
Unfortunately, the ternary operator doesn't support type promotion with null checks, which means I have to add !
to assert that it's not null in order to make it compile under null safety:
MyType convert(OtherType value) {
return MyType(
field1: value.field1,
field2: value.field2 != null ? MyWrapper(value.field2!) : null,
);
}
This makes the code a bit unsafe; one could easily image a scenario where the null check is modified or some code is copied and pasted into a situation where that !
causes a crash.
So my question is whether there is a specific best practice to handle this situation more safely? Rewriting the code to take advantage of flow analysis and type promotion directly is unwieldy:
MyType convert(OtherType value) {
final rawField2 = value.field2;
final MyWrapper? field2;
if (rawField2 != null) {
field2 = MyWrapper(rawField2);
} else {
field2 = null;
}
return MyType(
field1: value.field1,
field2: field2,
);
}
As someone who thinks a lot in terms of functional programming, my instinct is to think about about nullable types as a monad, and define map
accordingly:
extension NullMap<T> on T? {
U? map<U>(U Function(T) operation) {
final value = this;
if (value == null) {
return null;
} else {
return operation(value);
}
}
}
Then this situation could be handled like this:
MyType convert(OtherType value) {
return MyType(
field1: value.field1,
field2: value.field2.map((f) => MyWrapper(f)),
);
}
This seems like a good approach to maintain both safety and concision. However, I've searched long and hard online and I can't find anyone else using this approach in Dart. There are a few examples of packages that define an Optional
monad that seem to predate null safety, but I can't find any examples of Dart developers defining map
directly on nullable types. Is there a major "gotcha" here that I'm missing? Is there another approach this is both ergonomic and more conventional in Dart?
CodePudding user response:
Unfortunately, the ternary operator doesn't support type promotion with null checks
This premise is not correct. The ternary operator does do type promotion. However, non-local variables cannot be promoted. Also see:
- https://dart.dev/tools/non-promotion-reasons
- "The operator can’t be unconditionally invoked because the receiver can be null" error after migrating to Dart null-safety.
Therefore you should just introduce a local variable (which you seem to have already realized in your if
-else
and NullFlatMap
examples):
MyType convert(OtherType value) {
final field2 = value.field2;
return MyType(
field1: value.field1,
field2: field2 != null ? MyWrapper(field2) : null,
);
}