可以更简洁地使用 Scala 的 Option flatMap 方法吗?

Possible to make use of Scala's Option flatMap method more concise?

无可否认,我对 Scala 还是很陌生,而且我在使用许多 Scala 示例中看到的语法糖时遇到了麻烦。 它通常会产生一个非常简洁的陈述,但老实说到目前为止(对我来说)有点不可读。

因此,我希望采用选项 class 的典型用法,安全取消引用,作为开始理解的好地方,例如,在我的特定示例中使用下划线见过。

我找到了一篇非常好的文章,其中展示了使用 Option 避免 null 情况的示例。

https://medium.com/@sinisalouc/demystifying-the-monad-in-scala-cc716bb6f534#.fhrljf7nl

他这样描述一个用途:

trait User {
  val child: Option[User]
}

By the way, you can also write those functions as in-place lambda functions instead of defining them a priori. Then the code becomes this:

val result = UserService.loadUser("mike")
  .flatMap(user => user.child)
  .flatMap(user => user.child)

看起来不错!也许不像 groovy 中那样简洁,但也不错。

所以我想我会尝试将它应用到我正在尝试解决的案例中。

我有一个类型 Person,其中 Person 的存在是可选的,但如果我们有一个人,他的属性是有保证的。因此,Person 类型本身没有使用 Option 类型。

Person 有一个 Id 类型的 PID。 Id 类型由两个 String 类型组成; Id 类型和 Id 值。

我使用 Scala 控制台测试了以下内容:

class Id(val idCode : String, val idVal : String)

class Person(val pid : Id, val name : String)

val anId: Id = new Id("Passport_number", "12345")

val person: Person = new Person(anId, "Sean")

val operson : Option[Person] = Some(person)

好的。这设置了我的人,它是可选的实例。

我从上面的链接文章中了解到,我可以通过使用flatMap来获取Persons Id-Val;像这样:

val result = operson.flatMap(person => Some(person.pid)).flatMap(pid => Some(pid.idVal)).getOrElse("NoValue")

太棒了!这样可行。如果我实际上没有人,我的结果是 "NoValue".

我使用了 flatMap(而不是 Map),因为除非我误解(并且我对 Map 的测试不正确)如果我使用 Map,我必须提供替代或默认的 Person 实例。我不想这样做。

好的,所以,flatMap 是要走的路。

不过,这确实不是一个很简洁的说法。 如果我用 groovy 的风格来写,我想我可以做这样的事情:

val result = person?.pid.idVal

哇,好多了!

Scala 肯定有办法提供至少与 Groovy 一样好的东西?

在上面的链接示例中,他能够使用我之前提到的一些语法糖使他的陈述更加简洁。下划线:

or even more concise:

val result = UserService.loadUser("mike")
  .flatMap(_.child)
  .flatMap(_.child)

因此,在这种情况下,下划线字符似乎允许您跳过指定类型(因为推断类型)并将其替换为下划线。

然而,当我用我的例子尝试同样的事情时:

val result = operson.flatMap(Some(_.pid)).flatMap(Some(_.idVal)).getOrElse("NoValue")

Scala 抱怨。

<console>:15: error: missing parameter type for expanded function ((x) => x.idVal)
       val result = operson.flatMap(Some(_.pid)).flatMap(Some(_.idVal)).getOrElse("NoValue")

有人可以帮助我吗?

我怎么会误解这个? 有没有一种速记法可以写出我上面冗长的陈述? flatMap 是实现我所追求目标的最佳方式吗?或者有更好更简洁 and/or 可读的方法吗?

提前致谢!

为什么坚持使用flatMap?我只是用 map 作为你的例子:

val result = operson.map(_.pid).map(_.idVal).getOrElse("NoValue")

甚至更短:

val result = operson.map(_.pid.idVal).getOrElse("NoValue")

您应该只将 flatMap 与 return Option 的函数一起使用。您的 pididVal 不是 Option,因此只需映射它们即可。

你说

I have a type Person where the existence of a Person is optional, but if we have a person, his attributes are guaranteed. For that reason, there are no use of the Option type within the Person type itself.

这是您的示例与User示例的本质区别。在 User 示例中,User 实例的存在及其 child 字段都是选项。这就是为什么要获得 child,您需要 flatMap。但是,由于在您的示例中,无法保证仅存在 Person,因此在检索到 Option[Person] 后,您可以安全地映射到其任何字段。

flatMap 视为 map,然后是 flatten(因此得名)。如果我映射到 child:

val ouser = Some(new User())
val child: Option[Option[User]] = ouser.map(_.child)

我会得到一个 Option[Option[User]]。我需要将其展平到一个 Option 级别,这就是我首先使用 flatMap 的原因。

如果您正在寻找最简洁的解决方案,请考虑以下内容:

val result = operson.fold("NoValue")(_.pid.idVal)

虽然可能会觉得不清楚或令人困惑