I am familiar with the concept that Scala's for
comprehensions are just syntactic sugar for monadic operations (map
, withFilter
, foreach
, and flatMap
) and the desugaring is described in this popular answer.
By that logic, I was surprised to find that, when using pattern matching as part of the assignment portion of a for
comprehension, no MatchError
is thrown when the pattern doesn't match an element. Instead, the non-matched element is filtered out:
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")!
I would have expected that comprehension to translate to something like this:
val adminIds = accounts.map { case Account(id, "ADMIN") => id }
// or maybe
val adminIds = accounts.map { account =>
val Account(id, "ADMIN") = account
id
}
But of course those would throw a MatchError
. Instead it seems more similar to this:
val adminIds = accounts.collect { case Account(id, "ADMIN") => id }
However, I've never seen any mention of a for
comprehension desugaring into a collect
call.
So how is this done under the hood?
CodePudding user response:
Adding my answer, that was originally posted as a comment.
Under the hood, your for-comprehension is translated to:
val adminIds = accounts.withFilter {
case Account(id: String, "ADMIN") => true;
case _ => false
}.map({
case Account(id: String, "ADMIN") => id
})
In other words, it's using withFilter
.
Summarizing from the linked answer, withFilter
was introduced in scala 2.8 and applies to strict collections (like List
, as opposed to non-strict collections like Stream
.) Instead of returning a new, filtered collection, it filters on-demand.
That's why you don't notice a MatchError
when running your code.
CodePudding user response:
If you put that code in an IDE like Intellij and desugar you will get something like this
val adminIds: Set[String] = accounts.withFilter { case Account(id: String, "ADMIN") => true; case _ => false }.map({ case Account(id: String, "ADMIN") => id })
It's not a call to collect