用于理解的选项生成器强制其他生成器成为选项?

Option generator in for comprehension forces other generators to be Option?

第一个 for 表达式是基本示例。第二个 for-expr 引入了一个轻微的变化,我认为它会产生相同的输出。但因编译错误而失败。是什么原因以及如何解决?

for {
    n <- List(1,2)
    c <- "ABC"
} yield s"$c$n"
//res0: List[String] = List(A1, B1, C1, A2, B2, C2)

for {
    opt <- List(None, Some(1), None, None, Some(2), None)
    n <- opt
    c <- "ABC"
} yield s"$c$n"
//Error:(14, 5) type mismatch;
//found   : scala.collection.immutable.IndexedSeq[String]
//required: Option[?]
//  c <- "ABC"
//     ^

回复题主问题:对,第一个生成器"sets the mood"对整个for-expression。请注意,像上面那样的 for-expressions desugared 调用 flatMaps 和最后的 map (加上 withFilter 用于守卫).

在您的例子中,第一个 for-expression 脱糖 为以下表达式:

List(1, 2).flatMap(n => "ABC".map(c => s"$c$n"))

这是可行的,因为 Predef(在每个 Scala 程序中隐式导入)提供了从 StringSeq[Char] 的隐式转换。

val implicitlyConverted: Seq[Char] = "ABC"

因此,它按计划类型检查 运行。

现在让我们看看第二个for-expressions是如何脱糖的:

List(None, Some(1), None, None, Some(2), None).flatMap(opt => opt.flatMap(n => "ABC".map(c => s"$c$n")))

同样,我们有与上面相同的类型错误,如果我们将表达式分成几行,我们可能会更好地看到问题:

List(None, Some(1), None, None, Some(2), None).flatMap {
  opt =>
    opt.flatMap {
      n =>
        "ABC".map {
          c =>
            s"$c$n"
        }
    }
}

这给了我们以下错误:

<console>:12: error: type mismatch;
 found   : scala.collection.immutable.IndexedSeq[String]
 required: Option[?]
                      "ABC".map {
                                ^

第二个 flatMap 期望结果为 Option[_],而 ("ABC".map(...)) returns 中的 mapIndexedSeq[String]

所以,这就是原因。我们如何解决这个问题?最简单的解决方案可能涉及使用守卫并强制提取 Option 中的值,如下所示:

for {
  n <- List(None, Some(1), None, None, Some(2), None)
  c <- "ABC" if n.isDefined
} yield s"$c${n.get}"

解决此特定问题的更通用方法涉及 monad 转换器,因为问题的根源在于 monad 不组合;这个问题非常广泛和复杂,也许 this reply 可以给你一个更笼统的答案。

另一种"fix"的方法是改变顺序:

for {
    opt <- List(None, Some(1), None, None, Some(2), None)
    c <- "ABC"
    n <- opt
} yield s"$c$n"
//> List[String] = List(A1, B1, C1, A2, B2, C2)

之所以有效,是因为 Scala 可以将 Option 转换为 List,但不能反向转换。

其实比简单的转换要复杂的多,涉及到CanBuildFrom。通常 monad 根本不进行组合,但在 Scala 中,有些 monad 通过使用 CanBuildFrom.

来组合