Skip to content

hrhino/better-monadic-for

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

Getting started

The plugin is available on Maven Central.

addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.2.0")

Supports Scala 2.11 and 2.12.

Features

Desugaring for patterns without withFilters (-P:bm4:no-filtering:y)

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.

As an added bonus, type ascriptions on left-hand side do not become an isInstanceOf check.

def getThing: IO[String] = ???

for {
  x: String <- getCounts
} yield s"Count was $x"

desugars directly to

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

This also works with flatMap and foreach, of course.

No silent truncation of data

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

Match warnings

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]                      ^

Final map optimization (-P:bm4:no-map-id:y)

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 (-P:bm4:no-tupling:y)

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.

Notes

  • This plugin introduces no extra identifiers. It only affects the behavior of for-comprehension.
  • Regular if guards are not affected, only generator arrows.

License

MIT

About

Desugaring scala `for` without implicit `withFilter`s

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Scala 100.0%