在 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是孤立的,但不是在这个函数的上下文中。
还有另一种方法来查看问题,并通过这种观点直观地推导出上面的代码吗?
答案 0 :(得分:1)
只是为了澄清问题,这就是问题所在:
编写一个函数序列,将一个Options列表组合成一个Option,其中包含原始列表中所有Some值的列表。如果原始列表包含None甚至一次,则函数的结果应为None;否则结果应该是一些带有所有值的列表。
换句话说,sequence
按顺序计算参数中传递的列表中的每个选项。如果在评估此序列时找到None,则函数将停止并返回None,而不处理列表的其余部分。
flatMap
允许链接两个计算,其中一个取决于另一个的结果:
Option(42).flatMap(i => Option(i + 1))
创建的第二个选项(Option(i + 1)
)取决于第一个选项获得的结果(i
)。关于map
和flatMap
的一个有趣的事情是,给定函数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]]
为Nil
或empty
,则只返回空列表选项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
的值附加到其中。