理解 flatMap 和 Map

Understanding flatMap and Map

Scala 中的函数式编程(ISBN:978-1617290657)一书的练习 4.4 中,我们需要 运行 通过选项列表,并连接这些选项中的值转换为包含列表的单个选项。如果初始列表包含 None 选项,则最终选项也应为 None。

函数签名

def sequence[A](a: List[Option[A]]): Option[List[A]]

示例 1

scala> sequence(List(Some(3), Some(5), Some(1)))
Option[List[Int]] = Some(List(3, 5, 1))

示例 2

scala> sequence(List(Some(3), None, Some(1)))
Option[List[Int]] = None

这是我在网上找到的解决方案:

def sequence[A](a: List[Option[A]]): Option[List[A]] = a match {
  case Nil => Some(Nil)
  case h :: t => h flatMap (hh => sequence(t) map (hh :: _))
}

尽管我尽了最大的努力来理解这个实现(通过在纸上写下函数跟踪),但我无法直观地掌握这个函数。我能够单独理解 map 和 flatMap 的含义,但不能理解此函数的上下文。

有没有换个角度看问题,从那个角度直观推导出上面的代码?

所以...您的函数应该从 optionList: List[Option[A]] 中逐个获取 elem: Option[A] 个元素,并将它们累积到 acc: Option[List[A]].

现在,第一个案例case Nil => Some(Nil)应该很容易理解了。正如它所说,如果输入 List[Option[A]]Nilempty 那么只是 return 一个空列表的选项,即 Some(Nil).

至于第二部分,可以写的更清楚,

case head :: tail => h.flatMap(headValue => {
  val tailResult = sequence(tail)
  tailResult.map(tailResultValue => headValue :: tailResultValue)
)

它在 list 的头部获取 head: Option[A],然后递归地将 sequence 函数应用于列表的其余部分 tail 以获得 tailResult 这将是一个 Option[List[A]]。现在,它将 tailResult 的值映射为 List[A] 并将当前 head 的值附加到它。

为了澄清问题,这里是问题所在:

Write a function sequence that combines a list of Options into one Option containing a list of all the Some values in the original list. If the original list contains None even once, the result of the function should be None; otherwise the result should be Some with a list of all the values.

换句话说,sequence 依次计算传入参数列表中的每个选项。如果在评估此序列时发现 None,函数将停止并 returns 一个 None,而不处理列表的其余部分。

flatMap 允许链接两个计算,其中一个计算取决于另一个计算的结果:

Option(42).flatMap(i => Option(i + 1))

创建的第二个Option(Option(i + 1)),取决于第一个Option得到的结果(i)。关于 mapflatMap 的一件有趣的事情是给定函数 f: A => Option[A]map 保留计算链的中间结果,而 flatMap 跳过它们。这就是为什么:

val am: Option[Option[A]] = Option(a).map(a => f(a))
val af: Option[A] = Option(a).flatMap(a => f(a))

回到我们的问题,考虑到这一点,函数 sequence 可以表示为按顺序链接的每个计算获得的结果列表,包裹在同类计算中。

def sequence[A](a: List[Option[A]]): Option[List[A]] = a match {
    case Nil => Some(Nil)
    case h :: t => h flatMap (hh => sequence(t) map (hh :: _))
}

为了理解这个实现,你需要理解递归是如何工作的。不要再考虑此代码的每个元素如何评估(如何)并尝试专注于什么(就像在数学中一样)。鉴于:

h flatMap (_ => sequence(t))

表示在所有先前的计算都已成功评估后,传入参数的序列中评估的最后一个计算。因此:

h flatMap (hh => sequence(t) map (hh :: _))

是评估这个计算链时获得的每个中间结果的累加。我建议您查看函数式编程中的 traverse 函数和 Applicative 的概念。这两个概念正是 sequence.

的定义

我希望这能回答你的问题。