Skip to content
Oeiuwq Faith Blog OpenSource Porfolio

oleg-py/better-monadic-for

Desugaring scala `for` without implicit `withFilter`s

oleg-py/better-monadic-for.json
{
"createdAt": "2018-03-29T19:47:54Z",
"defaultBranch": "master",
"description": "Desugaring scala `for` without implicit `withFilter`s",
"fullName": "oleg-py/better-monadic-for",
"homepage": null,
"language": "Scala",
"name": "better-monadic-for",
"pushedAt": "2024-05-07T13:24:27Z",
"stargazersCount": 713,
"topics": [
"compiler-plugin",
"desugar",
"for-comprehension",
"functional-programming",
"optimization",
"scala"
],
"updatedAt": "2025-11-20T12:07:24Z",
"url": "https://github.com/oleg-py/better-monadic-for"
}

Gitter Waffle.io - Columns and their card count Maven central

A Scala compiler plugin to give patterns and for-comprehensions the love they deserve

Scala 3.0.0 natively supports the semantic changes provided by better-monadic-for under -source:future compiler flag. The following code is considered valid under this flag:

for {
(x, given String) <- IO(42 -> "foo")
} yield s"$x${summon[String]}"

There are no changes to map desugaring and value bindings inside fors still allocate tuples to my current knowledge. I don’t currently have plans on rewriting plugin for Scala 3, however.

See changes: pattern bindings and contextual abstractions: pattern-bound given instances.


The plugin is available on Maven Central.

addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<configuration>
<compilerPlugins>
<compilerPlugin>
<groupId>com.olegpy</groupId>
<artifactId>better-monadic-for_2.13</artifactId>
<version>0.3.1</version>
</compilerPlugin>
</compilerPlugins>
</configuration>
</plugin>

Supports Scala 2.11, 2.12, and 2.13.1

Available plugin options

All options have form of -P:bm4:$feature:$flag

FeatureFlag (default)
Desugaring without withFilter-P:bm4:no-filtering:y
Elimination of identity map-P:bm4:no-map-id:y
Elimination of tuples in bindings-P:bm4:no-tupling:y
Implicit definining patterns-P:bm4:implicit-patterns:y

Supported values for flags:

  • Disabling: n, no, 0, false
  • Enabling: y, yes, 1, true

Changelog
VersionChanges
0.3.1Fix issues with wartremover, implicit patterns with = binds & Xplugin-list flag
0.3.0-M4Fix anonymous variables in Scala 2.12.7+
M2, M3Fixes for implicit patterns
0.3.0-M1Initial implementation of implicit patterns
0.2.4Fixed: incompatibility with Dsl.scala
0.2.3Fixed: if-guards were broken when using untupling
0.2.2Fixed: destructuring within for bindings (bar, baz) = foo
0.2.1Fixed: untupling with -Ywarn-unused:locals causing warnings on e.g. _ = println().
0.2.0Added optimizations: map elimination & untupling. Added plugin options.
0.1.0Initial version featuring for desugaring without withFilters.

Desugaring for patterns without withFilters

Section titled “Desugaring for patterns without withFilters”

Destructuring Either / IO / Task / FlatMap[F]

Section titled “Destructuring Either / IO / Task / FlatMap[F]”

This plugin lets you do:

import cats.implicits._
import cats.effect.IO
def getCounts: IO[(Int, Int)] = ???
for {
(x, y) <- getCounts
} yield x + y

With regular Scala, this desugars to:

getCounts
.withFilter((@unchecked _) match {
case (x, y) => true
case _ => false
}
.map((@unchecked _) match {
case (x, y) => x + y
}

Which fails to compile, because IO does not define withFilter

This plugin changes it to:

getCounts
.map(_ match { case (x, y) => x + y })

Removing both withFilter and unchecked on generated map. So the code just works.

Additional Effects

Type ascriptions on left-hand side do not become an isInstanceOf check - which they do by default. E.g.

def getThing: IO[String] = ???
for {
x: String <- getCounts
} yield s"Count was $x"

would desugar directly to

getCounts.map((x: String) => s"Count was $x")

This also works with flatMap and foreach, of course.

This example is taken from Scala warts post by @lihaoyi

// Truncates 5
for((a, b) <- Seq(1 -> 2, 3 -> 4, 5)) yield a + " " + b
// Throws MatchError
Seq(1 -> 2, 3 -> 4, 5).map{case (a, b) => a + " " + b}

With the plugin, both versions are equivalent and result in MatchError

Generators will now show exhaustivity warnings now whenever regular pattern matches would:

import cats.syntax.option._
for (Some(x) <- IO(none[Int])) yield x
D:\Code\better-monadic-for\src\test\scala\com\olegpy\TestFor.scala:66
:22: match may not be exhaustive.
[warn] It would fail on the following input: None
[warn] for (Some(x) <- IO(none[Int])) yield x
[warn] ^

Eliminate calls to .map in comprehensions like this:

for {
x <- xs
y <- getYs(x)
} yield y

Standard desugaring is

xs.flatMap(x => getYs(x).map(y => y))

This plugin simplifies it to

xs.flatMap(x => getYs(x))

Desugar bindings as vals instead of tuples

Section titled “Desugar bindings as vals instead of tuples”

Direct fix for lampepfl/dotty#2573. If the binding is not used in follow-up withFilter, it is desugared as plain vals, saving on allocations and primitive boxing.

Define implicits in for-comprehensions or matches

Section titled “Define implicits in for-comprehensions or matches”

Since version 0.3.0-M1, it is possible to define implicit values inside for-comprehensions using a new keyword implicit0:

case class ImplicitTest(id: String)
for {
x <- Option(42)
implicit0(it: ImplicitTest) <- Option(ImplicitTest("eggs"))
_ <- Option("dummy")
_ = "dummy"
_ = assert(implicitly[ImplicitTest] eq it)
} yield "ok"

In current version (0.3.0) it’s required to specify a type annotation in a pattern with implicit0.

It also works in regular match clauses:

(1, "foo", ImplicitTest("eggs")) match {
case (_, "foo", implicit0(it: ImplicitTest)) => assert(implicitly[ImplicitTest] eq it)
}
  • This plugin reserves one extra keyword, implicit0, if corresponding option for implicit patterns is enabled (which is by default).
  • Regular if guards are not affected, only generator arrows.

MIT