Home > Software engineering >  How do pattern match assignments in for comprehensions translate into monadic operations?
How do pattern match assignments in for comprehensions translate into monadic operations?

Time:10-16

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

  • Related