'Pattern(...) <-' always adds '.withFilter': Scala ritual

DAML and Haskell users are used to writing irrefutable patterns on the left of <- in a do block, such as to destructure a tuple. However, if you write the same in Scala,

for {
  (a, b) <- Some((1, 2))
} yield a + b

it will desugar like so:

Some((1, 2))
  .withFilter(check$ifrefutable$1 => check$ifrefutable$1: @unchecked match {
    case ((a @ _), (b @ _)) => true
    case _ => false
  })
  .map(x$1 => x$1: @unchecked match {
    case ((a @ _), (b @ _)) => a + b
  })

No annotations either

As with case, there are no type annotations to the left of <-, only type patterns:

for {x: Int <- List(1, 2)} yield x

// desugars to

List(1, 2)
  .withFilter(check$ifrefutable$1 => check$ifrefutable$1: @unchecked match {
    case (x @ (_: Int)) => true
    case _ => false
  })
  .map(x: Int => x)

Fixing the pattern

The fix for destructuring is to separate the destructuring from the <- binding:

for {
  ab <- Some((1, 2))
  (a, b) = ab
} yield a + b

which desugars like so:

Some((1, 2))
  .map { ab => 
    val x$2 = ab: @unchecked match {
      case (x$1 @ ((a @ _), (b @ _))) => (x$1, a, b)
    }
    val x$1 = x$2._1
    val a = x$2._2
    val b = x$2._3
    (ab, x$1)
  }
  .map(x$3 => x$3: @unchecked match {
    case ((ab @ _), ((a @ _), (b @ _))) => a + b
  })

It’s longer, but it works with types that can’t be filtered, and for those that can, at least it won’t silently drop if your pattern turned out to be not-so-irrefutable.

Fixing the annotation

For type annotations, the fix is to use an ascription on the expression instead:

for {x <- List(1, 2): List[Int]} yield x

Seeing how something desugars

If you are curious how any expression desugars in Scala, you can use reify in the REPL:

scala> import reflect.runtime.universe.reify
import reflect.runtime.universe.reify

scala> reify { for {x <- List(1, 2): List[Int]} yield x }
res11: reflect.runtime.universe.Expr[List[Int]] = Expr[List[Int]]((List.apply(1, 2): `package`.List[Int]).map(((x) => x))(List.canBuildFrom))

scala> reify { 2 :: Nil }
res12: reflect.runtime.universe.Expr[List[Int]] =
Expr[List[Int]]({
  <synthetic> <artifact> val x$1 = 2;
  Nil.$colon$colon(x$1)
})

Note that the output is a printed syntax tree rather than valid Scala code; nevertheless it is a good way to figure out what some piece of higher-level syntax will do in Scala.

Tested with Scala 2.12.11.

6 Likes