了解flatMap和Map

时间:2018-02-19 12:31:59

标签: scala functional-programming maybe

Scala中的函数编程(ISBN:978-1617290657)一书的练习4.4中,我们需要运行一个选项列表,并将这些选项中的值连接成一个包含的选项一个列表。如果初始List包含None选项,则最终Option也应为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是孤立的,但不是在这个函数的上下文中。

还有另一种方法来查看问题,并通过这种观点直观地推导出上面的代码吗?

2 个答案:

答案 0 :(得分:1)

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

  

编写一个函数序列,将一个Options列表组合成一个Option,其中包含原始列表中所有Some值的列表。如果原始列表包含None甚至一次,则函数的结果应为None;否则结果应该是一些带有所有值的列表。

换句话说,sequence按顺序计算参数中传递的列表中的每个选项。如果在评估此序列时找到None,则函数将停止并返回None,而不处理列表的其余部分。

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

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

创建的第二个选项(Option(i + 1))取决于第一个选项获得的结果(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的原因。

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

答案 1 :(得分:0)

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

现在,第一个案例case Nil => Some(Nil)应该很容易理解。如上所述,如果输入List[Option[A]]Nilempty,则只返回空列表选项Some(Nil)

至于第二部分,可以更清楚地写成如下,

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

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