{"id":15289154,"url":"https://github.com/monadless/monadless","last_synced_at":"2025-05-15T05:07:39.648Z","repository":{"id":19871207,"uuid":"88124997","full_name":"monadless/monadless","owner":"monadless","description":"Syntactic sugar for monad composition in Scala","archived":false,"fork":false,"pushed_at":"2024-08-12T23:44:55.000Z","size":333,"stargazers_count":280,"open_issues_count":16,"forks_count":17,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-11T15:57:07.041Z","etag":null,"topics":["cats","futures","monad","monix","scala","scalajs"],"latest_commit_sha":null,"homepage":"http://monadless.io","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/monadless.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":"docs/support.css","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-04-13T04:27:59.000Z","updated_at":"2025-03-31T09:34:27.000Z","dependencies_parsed_at":"2024-10-14T20:00:37.786Z","dependency_job_id":null,"html_url":"https://github.com/monadless/monadless","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monadless%2Fmonadless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monadless%2Fmonadless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monadless%2Fmonadless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monadless%2Fmonadless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/monadless","download_url":"https://codeload.github.com/monadless/monadless/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254276447,"owners_count":22043867,"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":["cats","futures","monad","monix","scala","scalajs"],"created_at":"2024-09-30T15:59:26.107Z","updated_at":"2025-05-15T05:07:34.638Z","avatar_url":"https://github.com/monadless.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"![monadless](https://raw.githubusercontent.com/monadless/monadless/master/monadless.png)\n\nSyntactic sugar for monad composition (or: \"async/await\" generalized)\n\n[![Build Status](https://travis-ci.org/monadless/monadless.svg?branch=master)](https://travis-ci.org/monadless/monadless)\n[![Codacy Badge](https://api.codacy.com/project/badge/grade/ea4068928617433f8275534af3351152)](https://www.codacy.com/app/fwbrasil/monadless)\n[![Join the chat at https://gitter.im/monadless/monadless](https://img.shields.io/badge/gitter-join%20chat-green.svg)](https://gitter.im/monadless/monadless?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)-[![Dependency Status](https://www.versioneye.com/user/projects/58f1b1915c12c800161e64d1/badge.svg?style=flat)](https://www.versioneye.com/user/projects/58f1b1915c12c800161e64d1)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.monadless/monadless_2.11/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.monadless/monadless_2.11)\n[![Javadocs](https://www.javadoc.io/badge/io.monadless/monadless_2.11.svg)](https://www.javadoc.io/doc/io.monadless/monadless-core_2.11)\n\n## Problem\n\nDealing with monad compositions involves considerable syntax noise. For instance, this code using the `Future` monad:\n\n```scala\ncallServiceA().flatMap { a =\u003e\n  callServiceB(a).flatMap { b =\u003e\n    callServiceC(b).map { c =\u003e\n      (a, c)\n    }\n  }\n}\n```\n\nwould be much easier to follow using synchronous operations, without a monad:\n\n```scala\n  val a = callServiceA()\n  val b = callServiceB(a)\n  val c = callServiceC(b)\n  (a, c)\n```\n\nThis issue affects the usability of any monadic interface (Future, Option, Try, etc.). As an alternative, Scala provides for-comprehensions to reduce the noise:\n\n```scala\n  for {\n    a \u003c- callServiceA()\n    b \u003c- callServiceB(a)\n    c \u003c- callServiceC(b)\n  } yield {\n    (a, c)\n  }\n```\n\nThey are useful to express sequential compositions and make it easy to access the results of each for-comprehension step from the following ones, but they don't provide syntax sugar for Scala constructs other than assignment (`\u003c-`, `=`) and mapping (`yield`).\n\n## Solution\n\nMost mainstream languages have support for asynchronous programming using the async/await idiom or are implementing it (e.g. F#, C#/VB, Javascript, Python, Swift). Although useful, async/await is usually tied to a particular monad that represents asynchronous computations (`Task`, `Future`, etc.).\n\nThis library implements a solution similar to async/await but generalized to any monad type. This generalization is a major factor considering that some codebases use other monads like `Task` in addition to `Future` for asynchronous computations.\n\nGiven a monad `M`, the generalization uses the concept of lifting regular values to a monad (`T =\u003e M[T]`) and unlifting values from a monad instance (`M[T] =\u003e T`). Example usage:\n\n```scala\nlift {\n  val a = unlift(callServiceA())\n  val b = unlift(callServiceB(a))\n  val c = unlift(callServiceC(b))\n  (a, c)\n}\n```\n\nNote that `lift` corresponds to `async` and `unlift` to `await`.\n\n## Getting started\n\nThe `lift` and `unlift` methods are provided by an instance of `io.monadless.Monadless`. The library is generic and can be used with any monad type, but sub-modules with pre-defined `Monadless` instances are provided for convenience:\n\n### `monadless-stdlib`\n\nSBT configuration:\n```scala\n// scala\nlibraryDependencies += \"io.monadless\" %% \"monadless-stdlib\" % \"0.0.13\"\n\n// scala.js\nlibraryDependencies += \"io.monadless\" %%% \"monadless-stdlib\" % \"0.0.13\"\n```\n\nImports:\n```scala\n// for `scala.concurrent.Future`\nimport io.monadless.stdlib.MonadlessFuture._\n\n// for `scala.Option`\n// note: doesn't support `try`/`catch`/`finally`\nimport io.monadless.stdlib.MonadlessOption._\n\n// for `scala.util.Try`\nimport io.monadless.stdlib.MonadlessTry._\n```\n\n### `monadless-monix`\n\nSBT configuration:\n```scala\n// scala\nlibraryDependencies += \"io.monadless\" %% \"monadless-monix\" % \"0.0.13\"\n\n// scala.js\nlibraryDependencies += \"io.monadless\" %%% \"monadless-monix\" % \"0.0.13\"\n```\n\nUsage:\n```scala\n// for `monix.eval.Task`\nimport io.monadless.monix.MonadlessTask._\n```\n\n### `monadless-cats`\n\nSBT configuration:\n```\n// scala\nlibraryDependencies += \"io.monadless\" %% \"monadless-cats\" % \"0.0.13\"\n\n// scala.js\nlibraryDependencies += \"io.monadless\" %%% \"monadless-cats\" % \"0.0.13\"\n```\n\nUsage:\n```scala\n// for `cats.Applicative`\n// note: doesn't support `try`/`catch`/`finally`\nval myApplicativeMonadless = io.monadless.cats.MonadlessApplicative[MyApplicative]()\nimport myApplicativeMonadless._\n\n// for `cats.Monad`\n// note: doesn't support `try`/`catch`/`finally`\nval myMonadMonadless = io.monadless.cats.MonadlessMonad[MyMonad]()\nimport myMonadMonadless._\n```\n\n### `monadless-algebird`\n\nSBT configuration:\n```\nlibraryDependencies += \"io.monadless\" %% \"monadless-algebird\" % \"0.0.13\"\n```\n\nUsage:\n```scala\n// for `com.twitter.algebird.Applicative`\n// note: doesn't support `try`/`catch`/`finally`\nval myApplicativeMonadless = io.monadless.algebird.MonadlessApplicative[MyApplicative]()\nimport myApplicativeMonadless._\n\n// for `com.twitter.algebird.Monad`\n// note: doesn't support `try`/`catch`/`finally`\nval myMonadMonadless = io.monadless.algebird.MonadlessMonad[MyMonad]()\nimport monadless._\n```\n\n### Twitter monads\n\nSBT configuration:\n```\nlibraryDependencies += \"io.monadless\" %% \"monadless-core\" % \"0.0.13\"\n```\n\nThe default method resolution uses the naming conventions adopted by Twitter, so it's possible to use the default `Monadless` for them:\n\n```scala\nval futureMonadless = io.monadless.Monadless[com.twitter.util.Future]()\nimport futureMonadless._\n\nval tryMonadless = io.monadless.Monadless[com.twitter.util.Try]()\nimport tryMonadless\n```\n\n### Other monads\n\nSBT configuration:\n```\n// scala\nlibraryDependencies += \"io.monadless\" %% \"monadless-core\" % \"0.0.13\"\n\n// scala.js\nlibraryDependencies += \"io.monadless\" %% \"monadless-core\" % \"0.0.13\"\n```\n\nSee [\"How does it work?\"](#how-does-it-work) for information on how to define a `Monadless` instance for other monads.\n\n## Supported constructs\n\n`val`s:\n```scala\nlift {\n  val i = unlift(a)\n  i + 1\n}\n```\n\nnested blocks of code:\n```scala\nlift {\n  val i = {\n     val j = unlift(a)\n     j * 3\n  }\n  i + 1\n}\n```\n\n`val` pattern matching:\n```scala\nlift {\n  val (i, j) = (unlift(a), unlift(b))\n}\n```\n\n`if` conditions:\n```scala\nlift {\n  if(unlift(a) == 1) unlift(c)\n  else 0\n}\n```\n\n`boolean` operations (including short-circuiting):\n```scala\nlift {\n  unlift(a) == 1 || (unlift(b) == 2 \u0026\u0026 unlift(c) == 3)\n}\n```\n\n`def`:\n```scala\nlift {\n  def m(j: Int) = unlift(a) + j\n  m(unlift(b))\n}\n```\n\nrecursive `def`s:\n```scala\nlift {\n  def m(j: Int) = if(j == 0) unlift(a) else m(j - 1)\n  m(10)\n}\n```\n\n`trait`s, `class`es, and `object`s:\n```scala\nlift {\n  trait A {\n    def i = unlift(a)\n  }\n  class B extends A {\n    def j = i + 1\n  }\n  object C {\n    val k = unlift(c)\n  }\n  (new B).j + C.k\n}\n```\n\npattern matching:\n```scala\nlift {\n  unlift(a) match {\n    case 1 =\u003e unlift(b)\n    case _ =\u003e unlift(c)\n  }\n}\n```\n\n`try`/`catch`/`finally`:\n```scala\nlift {\n  try unlift(a)\n  catch {\n    case e =\u003e unlift(b)\n  } finally {\n    println(\"done\")\n  }\n}\n```\n\n`while` loops:\n```scala\nlift {\n  var i = 0\n  while(i \u003c 10)\n    i += unlift(a)\n}\n```\n\nThe [`UnsupportedSpec`](https://github.com/monadless/monadless/blob/master/monadless-core/src/test/scala/io/monadless/UnsupportedSpec.scala) lists the constructs that are known to be unsupported. Please report if you find a construct that can't be translated and is not classified by the spec class.\n\n## How does it work?\n\nThe `unlift` method is only a marker that indicates that the `lift` macro transformation needs to treat a value as monad instance. For example, it never blocks threads using `Await.result` if it's dealing with a `Future`. \n\nThe code generated by the macro uses an approach similar to for-comprehensions, resolving at compile time the methods that are required for the composition and not requiring a particular monad interface. We call these \"ghost\" methods: they aren't defined by an interface and only need to be source-compatible with the generated macro tree. To elucidate, let's take `map` as an example:\n\n```scala\n// Option `map` signature\ndef map[B](f: A =\u003e B): Option[B]\n\n// Future `map` signature\ndef map[B](f: A =\u003e B)(implicit ec: ExecutionContext)\n```\n\n`Future` and `Option` are supported by for-comprehensions and `lift` even though they don't share the same method signature since `Future` requires an `ExecutionContext`. They are only required to be source-compatible with the transformed tree. Example `lift` transformation:\n\n```scala\ndef a: Future[Int] = ???\n\n// this transformation\nlift {\n  unlift(a) + 1\n}\n\n// generates the tree\na.map(_ + 1)\n\n// that triggers scala's implicit resolution after the\n// macro transformation and becomes:\na.map(_ + 1)(theExecutionContext)\n```\n\nFor-comprehensions use only two \"ghost\" methods: `map` and `flatMap`. To support more Scala constructs, Monadless requires additional methods. This is the definition of the \"ghost\" interface that Monadless expects:\n\n```scala\n\ntrait M[A] {\n  \n  // Applies the map function\n  def map[B](f: A =\u003e B): M[B]\n\n  // Applies `f` and then flattens the result\n  def flatMap[B](f: A =\u003e M[B]): M[B]\n  \n  // Recovers from a failure if the partial function \n  // is defined for the failure. Used to translate `catch` clauses.\n  def rescue(pf: PartialFunction[Throwable, M[A]]): M[A]\n\n  // Executes `f` regarless of the outcome (success/failure).\n  // Used to translate `finally` clauses.\n  def ensure(f: =\u003e Unit): M[A]\n}\n\nobject M {\n\n  // Creates a monad instance with the result of `f`\n  def apply[A](f: =\u003e A): M[A]\n\n  // Transforms multiple monad instances into one.\n  def collect[A](l: List[M[A]]): M[List[A]]\n}\n\n```\n\nAs an alternative to using the monad type methods directly since not all existing monads implement them, Monadless allows the user to define them separately:\n\n```scala\nobject CustomMonadless extends Monadless[M] {\n\n  // these are also \"ghost\" methods\n  def apply[A](f: =\u003e A): M[A] = ???\n  def collect[A](l: List[M[A]]): M[List[A]] = ???\n  def map[A, B](m: M[A])(f: A =\u003e B): M[B] = ???\n  def flatMap[A, B](m: M[A])(f: A =\u003e M[B]): M[B] = ???\n  def rescue[A](m: M[A])(pf: PartialFunction[Throwable, M[A]]): M[A] = ??\n  def ensure[A](m: M[A])(f: =\u003e Unit): M[A] = ???\n}\n```\n\nThe methods defined by the `Monadless` instance have precedence over the ones specified by the monad instance and its companion object\n\nRelated projects\n----------------\n\n- [scala-async](https://github.com/scala/async) (for scala `Future`s)\n- [effectful](https://github.com/pelotom/effectful) (for scalaz `Monad`s)\n- [each](https://github.com/ThoughtWorksInc/each) (also for scalaz `Monad`s)\n\n\nCode of Conduct\n---------------\n\nPlease note that this project is released with a Contributor Code of Conduct. By participating in this project, you agree to abide by its terms. See [CODE_OF_CONDUCT.md](https://github.com/monadless/monadless/blob/master/CODE_OF_CONDUCT.md) for details.\n\nLicense\n-------\n\nSee the [LICENSE](https://github.com/monadless/monadless/blob/master/LICENSE.txt) file for details.\n\nMaintainers\n-----------\n\n- @fwbrasil\n- @sameerparekh\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonadless%2Fmonadless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmonadless%2Fmonadless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonadless%2Fmonadless/lists"}