迁移到 Dart null 安全:迁​​移三元运算符 null 检查的最佳实践?单子方法是否太不传统了?

发布于 2025-01-16 15:28:59 字数 1600 浏览 2 评论 0原文

我正在将代码库迁移到 null 安全,其中包含大量如下代码:

MyType convert(OtherType value) {
  return MyType(
    field1: value.field1,
    field2: value.field2 != null ? MyWrapper(value.field2) : null,
  );
}

不幸的是,三元运算符不支持通过 null 检查进行类型提升,这意味着我必须将 ! 添加到断言它不为空,以便使其在空安全下编译:

MyType convert(OtherType value) {
  return MyType(
    field1: value.field1,
    field2: value.field2 != null ? MyWrapper(value.field2!) : null,
  );
}

这使得代码有点不安全;人们可以很容易地想象这样一种场景:空检查被修改,或者某些代码被复制并粘贴到 ! 导致崩溃的情况中。

所以我的问题是是否有特定的最佳实践来更安全地处理这种情况?重写代码以直接利用流分析和类型提升是很麻烦的:

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

作为一个在函数式编程方面思考很多的人,我的本能是将可空类型视为一个 monad,并定义 map 相应地:

extension NullMap<T> on T? {
  U? map<U>(U Function(T) operation) {
    final value = this;
    if (value == null) {
      return null;
    } else {
      return operation(value);
    }
  }
}

那么这种情况可以这样处理:

MyType convert(OtherType value) {
  return MyType(
    field1: value.field1,
    field2: value.field2.map((f) => MyWrapper(f)),
  );
}

这似乎是保持安全性和简洁性的好方法。然而,我在网上进行了长时间的搜索,但找不到其他人在 Dart 中使用这种方法。有一些定义 Optional monad 的包示例似乎早于 null 安全性,但我找不到任何 Dart 开发人员直接在可空类型上定义 map 的示例。这里有我遗漏的主要“陷阱”吗?在 Dart 中是否还有另一种既符合人体工程学又更传统的方法?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

咋地 2025-01-23 15:28:59

不幸的是,三元运算符不支持通过空检查进行类型提升

这个前提是不正确的。三元运算符确实进行类型提升。但是,只有本地变量或既是final又是私有的实例变量可以进行类型提升。另请参阅:

因此,您应该只引入一个局部变量(您似乎已经在 if-elseNullFlatMap 示例中实现了这一点):

MyType convert(OtherType value) {
  final field2 = value.field2;
  return MyType(
    field1: value.field1,
    field2: field2 != null ? MyWrapper(field2) : null,
  );
}

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, only local variables or instance variables that are both final and private can be type-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,
  );
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文