Home > OS >  Migrating to Dart null safety: best practice for migrating ternary operator null checks? Is a monadi
Migrating to Dart null safety: best practice for migrating ternary operator null checks? Is a monadi

Time:03-26

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:

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,
  );
}
  • Related