{"id":13682765,"url":"https://github.com/oleg-py/better-monadic-for","last_synced_at":"2025-04-12T20:45:59.630Z","repository":{"id":52606066,"uuid":"127340088","full_name":"oleg-py/better-monadic-for","owner":"oleg-py","description":"Desugaring scala `for` without implicit `withFilter`s","archived":false,"fork":false,"pushed_at":"2024-05-07T13:24:27.000Z","size":106,"stargazers_count":707,"open_issues_count":12,"forks_count":34,"subscribers_count":21,"default_branch":"master","last_synced_at":"2025-04-04T00:09:27.759Z","etag":null,"topics":["compiler-plugin","desugar","for-comprehension","functional-programming","optimization","scala"],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oleg-py.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-29T19:47:54.000Z","updated_at":"2025-02-21T20:25:12.000Z","dependencies_parsed_at":"2024-10-30T16:03:27.815Z","dependency_job_id":"72c7a210-5d64-460a-8b3e-c1305e3b0fcb","html_url":"https://github.com/oleg-py/better-monadic-for","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oleg-py%2Fbetter-monadic-for","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oleg-py%2Fbetter-monadic-for/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oleg-py%2Fbetter-monadic-for/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oleg-py%2Fbetter-monadic-for/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oleg-py","download_url":"https://codeload.github.com/oleg-py/better-monadic-for/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248631696,"owners_count":21136559,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler-plugin","desugar","for-comprehension","functional-programming","optimization","scala"],"created_at":"2024-08-02T13:01:52.827Z","updated_at":"2025-04-12T20:45:59.598Z","avatar_url":"https://github.com/oleg-py.png","language":"Scala","funding_links":[],"categories":["Scala","Table of Contents","Sbt plugins"],"sub_categories":["Sbt plugins"],"readme":"# better-monadic-for\n[![Gitter](https://img.shields.io/gitter/room/better-monadic-for/Lobby.svg?style=flat-square)](https://gitter.im/better-monadic-for/Lobby)\n[![Waffle.io - Columns and their card count](https://badge.waffle.io/oleg-py/better-monadic-for.svg?style=flat-square\u0026columns=backlog,gathering%20opinions)](https://waffle.io/oleg-py/better-monadic-for)\n![Maven central](https://img.shields.io/maven-central/v/com.olegpy/better-monadic-for_2.12.svg?style=flat-square)\n\nA Scala compiler plugin to give patterns and for-comprehensions the love they deserve\n\n## Note on Scala 3\nScala 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:\n\n```scala\nfor {\n  (x, given String) \u003c- IO(42 -\u003e \"foo\")\n} yield s\"$x${summon[String]}\"\n```\n\nThere 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.\n\nSee [changes: pattern bindings](https://dotty.epfl.ch/docs/reference/changed-features/pattern-bindings.html) and [contextual abstractions: pattern-bound given instances](https://dotty.epfl.ch/docs/reference/contextual/givens.html#pattern-bound-given-instances).\n\n---\n\n## Getting started\nThe plugin is available on Maven Central.\n\n### sbt\n\n```sbt\naddCompilerPlugin(\"com.olegpy\" %% \"better-monadic-for\" % \"0.3.1\")\n```\n\n### maven\n\n```xml\n\u003cplugin\u003e\n  \u003cgroupId\u003enet.alchim31.maven\u003c/groupId\u003e\n  \u003cartifactId\u003escala-maven-plugin\u003c/artifactId\u003e\n  \u003cconfiguration\u003e\n    \u003ccompilerPlugins\u003e\n      \u003ccompilerPlugin\u003e\n        \u003cgroupId\u003ecom.olegpy\u003c/groupId\u003e\n        \u003cartifactId\u003ebetter-monadic-for_2.13\u003c/artifactId\u003e\n        \u003cversion\u003e0.3.1\u003c/version\u003e\n      \u003c/compilerPlugin\u003e\n    \u003c/compilerPlugins\u003e\n  \u003c/configuration\u003e\n\u003c/plugin\u003e\n```\n\nSupports Scala 2.11, 2.12, and 2.13.1\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eAvailable plugin options\u003c/strong\u003e\u003c/summary\u003e\n  \n---\nAll options have form of `-P:bm4:$feature:$flag`\n\n  \n| Feature                           | Flag (default)\n|-----------------------------------|------------------------\n| Desugaring without withFilter     | `-P:bm4:no-filtering:y`\n| Elimination of identity map       | `-P:bm4:no-map-id:y`\n| Elimination of tuples in bindings | `-P:bm4:no-tupling:y`\n| Implicit definining patterns      | `-P:bm4:implicit-patterns:y`\n  \nSupported values for flags:\n  - Disabling: `n`, `no`, `0`, `false`\n  - Enabling: `y`, `yes`, `1`, `true`\n  \n---\n  \n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eChangelog\u003c/strong\u003e\u003c/summary\u003e\n\n---\n\n| Version | Changes\n|---------|-------------------------------------------------------------------------------------------\n| 0.3.1   | Fix issues with wartremover, implicit patterns with = binds \u0026 Xplugin-list flag\n| 0.3.0-M4| Fix anonymous variables in Scala 2.12.7+\n| M2, M3  | Fixes for implicit patterns\n| 0.3.0-M1| Initial implementation of implicit patterns\n| 0.2.4   | Fixed: incompatibility with [Dsl.scala](https://github.com/ThoughtWorksInc/Dsl.scala)\n| 0.2.3   | Fixed: if-guards were broken when using untupling\n| 0.2.2   | Fixed: destructuring within for bindings `(bar, baz) = foo`\n| 0.2.1   | Fixed: untupling with `-Ywarn-unused:locals` causing warnings on e.g. `_ = println()`.\n| 0.2.0   | Added optimizations: map elimination \u0026 untupling. Added plugin options.\n| 0.1.0   | Initial version featuring for desugaring without `withFilter`s.\n\n---\n\n\u003c/details\u003e\n\n# Features\n## Desugaring `for` patterns without `withFilter`s\n### Destructuring `Either` / `IO` / `Task` / `FlatMap[F]`\n\nThis plugin lets you do:\n```scala\nimport cats.implicits._\nimport cats.effect.IO\n\ndef getCounts: IO[(Int, Int)] = ???\n\nfor {\n  (x, y) \u003c- getCounts\n} yield x + y\n```\n\nWith regular Scala, this desugars to:\n```scala\ngetCounts\n  .withFilter((@unchecked _) match {\n     case (x, y) =\u003e true\n     case _ =\u003e false\n  }\n  .map((@unchecked _) match {\n    case (x, y) =\u003e x + y\n  }\n```\n\nWhich fails to compile, because `IO` does not define `withFilter`\n\nThis plugin changes it to:\n```scala\ngetCounts\n  .map(_ match { case (x, y) =\u003e x + y })\n```\nRemoving both `withFilter` and `unchecked` on generated `map`. So the code just works.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eAdditional Effects\u003c/b\u003e\u003c/summary\u003e\n\n### Type ascriptions on LHS\n\nType ascriptions on left-hand side do not become an `isInstanceOf` check - which they do by default. E.g.\n\n```scala\ndef getThing: IO[String] = ???\n\nfor {\n  x: String \u003c- getCounts\n} yield s\"Count was $x\"\n```\n\nwould desugar directly to\n\n```scala\ngetCounts.map((x: String) =\u003e s\"Count was $x\")\n```\n\nThis also works with `flatMap` and `foreach`, of course.\n\n### No silent truncation of data\n\nThis example is taken from [Scala warts post](http://www.lihaoyi.com/post/WartsoftheScalaProgrammingLanguage.html#conflating-total-destructuring-with-partial-pattern-matching) by @lihaoyi\n```scala\n// Truncates 5\nfor((a, b) \u003c- Seq(1 -\u003e 2, 3 -\u003e 4, 5)) yield a + \" \" +  b\n\n// Throws MatchError\nSeq(1 -\u003e 2, 3 -\u003e 4, 5).map{case (a, b) =\u003e a + \" \" + b}\n```\n\nWith the plugin, both versions are equivalent and result in `MatchError`\n\n### Match warnings\nGenerators will now show exhaustivity warnings now whenever regular pattern matches would:\n\n```scala\n        import cats.syntax.option._\n\n        for (Some(x) \u003c- IO(none[Int])) yield x\n```\n\n```\nD:\\Code\\better-monadic-for\\src\\test\\scala\\com\\olegpy\\TestFor.scala:66\n:22: match may not be exhaustive.\n[warn] It would fail on the following input: None\n[warn]         for (Some(x) \u003c- IO(none[Int])) yield x\n[warn]                      ^\n```\n\n\u003c/details\u003e\n\n## Final map optimization\n\nEliminate calls to `.map` in comprehensions like this:\n\n```scala\nfor {\n  x \u003c- xs\n  y \u003c- getYs(x)\n} yield y\n```\n\nStandard desugaring is\n\n```scala\nxs.flatMap(x =\u003e getYs(x).map(y =\u003e y))\n```\n\nThis plugin simplifies it to\n\n```scala\nxs.flatMap(x =\u003e getYs(x))\n```\n\n## Desugar bindings as vals instead of tuples\n\nDirect fix for [lampepfl/dotty#2573](https://github.com/lampepfl/dotty/issues/2573).\nIf the binding is not used in follow-up `withFilter`, it is desugared as\nplain `val`s, saving on allocations and primitive boxing.\n\n## Define implicits in for-comprehensions or matches\n\nSince version 0.3.0-M1, it is possible to define implicit values inside for-comprehensions using a new keyword `implicit0`:\n\n```scala\ncase class ImplicitTest(id: String)\n\nfor {\n  x \u003c- Option(42)\n  implicit0(it: ImplicitTest) \u003c- Option(ImplicitTest(\"eggs\"))\n  _ \u003c- Option(\"dummy\")\n  _ = \"dummy\"\n  _ = assert(implicitly[ImplicitTest] eq it)\n} yield \"ok\"\n```\n\nIn current version (0.3.0) it's required to specify a type annotation in a pattern with `implicit0`.\n\nIt also works in regular match clauses:\n```scala\n(1, \"foo\", ImplicitTest(\"eggs\")) match {\n  case (_, \"foo\", implicit0(it: ImplicitTest)) =\u003e assert(implicitly[ImplicitTest] eq it)\n}\n```\n\n# Notes\n- This plugin reserves one extra keyword, `implicit0`, if corresponding option for implicit patterns is enabled (which is by default).\n- Regular `if` guards are not affected, only generator arrows.\n\n# License\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foleg-py%2Fbetter-monadic-for","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foleg-py%2Fbetter-monadic-for","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foleg-py%2Fbetter-monadic-for/lists"}