for comprehensions 中的模式匹配分配如何转化为一元操作?

How do pattern match assignments in for comprehensions translate into monadic operations?

我很熟悉 Scala 的 for 理解只是单子操作的语法糖(mapwithFilterforeachflatMap) 并且在 this popular answer.

中描述了脱糖

按照这个逻辑,我惊讶地发现,当使用模式匹配作为 for 理解的赋值部分的一部分时,当模式不匹配时不会抛出 MatchError一个元素。而是过滤掉不匹配的元素:

case class Account(id: String, role: String)
val accounts = Set(Account("a", "ADMIN"), Account("b", "USER"), Account("c", "ADMIN"), Account("d", "USER"), Account("e", "USER"))

val adminIds = for (Account(id, "ADMIN") <- accounts) yield id
// Set("a", "c") (no MatchError on Account("b", "USER")!

我本以为理解会转化为这样的东西:

val adminIds = accounts.map { case Account(id, "ADMIN") => id }

// or maybe
val adminIds = accounts.map { account =>
  val Account(id, "ADMIN") = account
  id
}

但当然那些会抛出 MatchError。相反,它似乎更类似于此:

val adminIds = accounts.collect { case Account(id, "ADMIN") => id }

但是,我从未见过任何提及 for 理解去糖化为 collect 调用的情况。

那么这是如何在幕后完成的?

如果您将该代码放入像 Intellij 和 desugar 这样的 IDE 中,您将得到类似这样的东西

val adminIds: Set[String] = accounts.withFilter { case Account(id: String, "ADMIN") => true; case _ => false }.map({ case Account(id: String, "ADMIN") => id })

不是来电收款

添加我的答案,该答案最初作为评论发布。

在幕后,您的理解翻译为:

val adminIds = accounts.withFilter {
  case Account(id: String, "ADMIN") => true;
  case _ => false
}.map({
  case Account(id: String, "ADMIN") => id
})

换句话说,它使用withFilter

linked answer 总结,withFilter 是在 scala 2.8 中引入的,适用于严格集合(如 List,而不是像 Stream 这样的非严格集合.) 它不是返回一个新的过滤集合,而是按需过滤。

这就是为什么您在 运行 您的代码中没有注意到 MatchError 的原因。