理解 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]]
是 Nil
或 empty
那么只是 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
)。关于 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
.
的定义
我希望这能回答你的问题。
在 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]]
是 Nil
或 empty
那么只是 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
)。关于 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
.
我希望这能回答你的问题。