{"id":20772456,"url":"https://github.com/rcardin/raise4s","last_synced_at":"2025-06-28T01:34:37.417Z","repository":{"id":232266247,"uuid":"783890814","full_name":"rcardin/raise4s","owner":"rcardin","description":"Porting of the Raise DSL from the Arrow Kt Kotlin library","archived":false,"fork":false,"pushed_at":"2024-04-13T10:31:09.000Z","size":43,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-04-13T21:46:53.377Z","etag":null,"topics":["direct-style","error-handling","functional-programming","scala","scala3"],"latest_commit_sha":null,"homepage":"","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/rcardin.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}},"created_at":"2024-04-08T19:25:13.000Z","updated_at":"2024-04-15T08:44:00.996Z","dependencies_parsed_at":"2024-04-13T11:29:47.073Z","dependency_job_id":null,"html_url":"https://github.com/rcardin/raise4s","commit_stats":null,"previous_names":["rcardin/raise4s"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fraise4s","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fraise4s/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fraise4s/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fraise4s/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rcardin","download_url":"https://codeload.github.com/rcardin/raise4s/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243111401,"owners_count":20238168,"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":["direct-style","error-handling","functional-programming","scala","scala3"],"created_at":"2024-11-17T12:21:22.914Z","updated_at":"2025-03-11T20:37:26.807Z","avatar_url":"https://github.com/rcardin.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/rcardin/raise4s/scala.yml?branch=main)\n![Maven Central](https://img.shields.io/maven-central/v/in.rcard.raise4s/core_3)\n![GitHub release (latest by date)](https://img.shields.io/github/v/release/rcardin/raise4s)\n[![javadoc](https://javadoc.io/badge2/in.rcard.raise4s/core_3/javadoc.svg)](https://javadoc.io/doc/in.rcard.raise4s/core_3)\n\n# Raise4s\n\nInitially, the library was a port of the Raise DSL from the Arrow Kt Kotlin library. Now, it has diverged from the original library and has become a new library, implementing the effect of failing with a typed error.\n\nAvailable modules are:\n\n- `core`: The Raise DSL for Scala 3.\n- `cats-raise4s`: The Raise DSL for Scala 3 with some useful Cats data structures.\n- `munit-raise4s`: MUnit integration for the Raise DSL.\n\n(See the README.md files in the subdirectories for more information.)\n\n## Dependency\n\nThe library is available on Maven Central. To use it, add the following dependency to your `build.sbt` files:\n\n```sbt\nlibraryDependencies += \"in.rcard.raise4s\" %% \"core\" % \"0.5.0\"\n```\n\nThe library is only available for Scala 3.\n\n## Usage\n\n### The `Raise` DSL in Scala\n\nThe Raise DSL is a new way to handle typed errors in Scala. Instead of using a wrapper type to address both the happy\npath and errors, the `Raise[E]` type describes the possibility that a function can raise a logical error of type `E`. A\nfunction that can raise an error of type `E` must execute in a scope that can also handle the error. In recent Scala,\nit's something that is referred to _direct style_.\n\nThe easiest way to define a function that can raise an error of type `E` is to create a context function using\nthe `Raise[E]` the implicit parameter:\n\n```scala 3\nimport in.rcard.raise4s.*\n\ncase class User(id: String, name: String)\n\nsealed trait Error\n\ncase class UserNotFound(id: String) extends Error\n\ndef findUserById(id: String): Raise[Error] ?=\u003e User = User(id, \"Alice\")\n```\n\nWe can do better than that, using the `infix type raises`:\n\n```scala 3\ndef findUserById(id: String): User raises Error = User(id, \"Alice\")\n```\n\nHow do we read the above syntax? The function `findUserById` returns a `User` and can raise an error of type `Error` or any of its subtypes.\n\nThe above function let us short-circuit an execution and raise an error of type `UserNotFound` using the `raise` function:\n\n```scala 3\ndef findUserById(id: String): User raises Error =\n  if (id == \"42\") User(id, \"Alice\") else Raise.raise(UserNotFound(id))\n```\n\nThe type of error a function can raise is checked at compile time. If we try to raise an error of a different type, the\ncompiler will complain:\n\n```scala 3\ndef findUserById(id: String): User raises Error =\n  if (id == \"42\") User(id, \"Alice\") else Raise.raise(\"User not found\")\n```\n\nThe above code will not compile with the following error:\n\n```\n[error] 9 |  if (id == \"42\") User(id) else Raise.raise(\"User not found\")\n[error]   |                                                       ^\n[error]   |No given instance of type in.rcard.raise4s.Raise[String] was found for parameter raise of method raise in package in.rcard.raise4s\n[error] one error found\n```\n\nIt's also possible to lift a value to the context `Raise[E] ?=\u003e A`. If we want to lift a value of type `A` to the\ncontext `Raise[Nothing]`, we can use the `succeed` function:\n\n```scala 3\ndef aliceUser: User raises UserNotFound = Raise.succeed(User(\"42\", \"Alice\"))\n```\n\nMore useful is to lift a value of type `E` as the error in the context `Raise[E]`:\n\n```scala 3\ndef userNotFound: User raises UserNotFound = UserNotFound(\"42\").raise[User]\n```\n\nWe can always rewrite che last function as follows:\n\n```scala 3\ndef userNotFound: User raises UserNotFound = {\n  Raise.raise(UserNotFound(\"42\"))\n}\n```\n\nWe may have noticed that one advantage of using the `Raise[E]` context is that the return type of the function listed\nonly the happy path. As we’ll see in a moment, this is a huge advantage when we want to compose functions that can raise\nerrors.\n\nAs you might guess from the previous compiler error, the Raise DSL is using implicit resolutions under the hood. In\nfact, to execute a function that uses the Raise DSL we need to provide an instance of the `Raise[E]` type class for the\nerror type `E`. The most generic way to execute a function that can raise an error of type `E` and that is defined in\nthe context of a `Raise[E]` is the `fold` function:\n\n```scala 3\nRaise.fold(\n  block = {\n    findUserById(\"43\")\n  },\n  catchBlock = ex =\u003e println(s\"Error: $ex\"),\n  recover = error =\u003e println(s\"User not found: $error\"),\n  transform = user =\u003e println(s\"User found: $user\")\n)\n```\n\nLet’s split the above function into parts. The `block` parameter is the function that we want to execute.\nThe `catchBlock` parameter is a function that is executed when the `block` function throws an exception. Don't worry:\nThe lambda handles only `NonFatal` exceptions. The `recover` parameter is a function that is executed when the `block`\nfunction raises a logical typed error of type `E`. Finally, the `transform` parameter is a function that is executed\nwhen the block function returns a value of type `A`, which is the happy path. All the handling blocks return the exact\nvalue of type `B`.\n\nThe `fold` function “consumes” the context, creating a concrete instance of a `Raise[E]` type and executing the `block`\nlambda in the context of that instance.\n\nThere are other flavors of the `fold` function. So, please, be sure to check them in the documentation.\n\nFor those who are not interested in handling possible exceptions raised by a function, there is a more straightforward\nfunction available, called `run`:\n\n```scala 3\nval maybeUser: Error | User = Raise.run {\n  findUserById(\"42\")\n}\n```\n\nPlease be aware that any exception thrown inside the `Raise[E]` context will bubble up and not be transformed\nautomatically into a logical typed error. What if we want to convert the exception into a typed error? For example, we\nwant to convert the `IllegalArgumentException` into a `UserNotFound`. Well, we can do it using a function\ncalled `catching`:\n\n```scala 3\ndef findUserByIdWithEx(id: String): User =\n  if (id == \"42\") User(id, \"Alice\") else throw new IllegalArgumentException(s\"User not found with id: $id\")\n\nval maybeUser: Either[Error, User] =\n  Raise.either:\n    Raise.catching[User](() =\u003e findUserByIdWithEx(\"42\")) {\n      case _: IllegalArgumentException =\u003e Raise.raise(UserNotFound(\"42\"))\n    }\n```\n\nThere is also a version of the `catching` function defined as an extension method of any `A` type. The above code can be\nrewritten as follows:\n\n```scala 3\nfindUserByIdWithEx(\"42\").catching {\n  case _: IllegalArgumentException =\u003e Raise.raise(UserNotFound(\"42\"))\n}\n```\n\nWe will see the `either` function in a moment. As we can see, there’s nothing special with the `catching` function. It\njust catches the exception and calls the catch lambda with the exception. The `catching` function lets the fatal\nexception bubble up.\n\nIt’s a different story if we want to recover or react to a typed error. In this case, we can use the `recover` function:\n\n```scala 3\ncase class NegativeAmount(amount: Double) extends Error\n\ndef convertToUsd(amount: Double, currency: String): Double raises NegativeAmount =\n  if (amount \u003c 0) Raise.raise(NegativeAmount(amount))\n  else amount * 1.2\n\nval usdAmount: Double =\n  Raise.recover({\n    convertToUsd(-1, \"EUR\")\n  }) { case NegativeAmount(amount) =\u003e 0.0D }\n```\n\nIf you don't care about the error type, and you want to recover from any error with a fixed value, you can use the `withDefault` function:\n\n```scala 3\nval usdAmount: Double =\n  Raise.withDefault(0.0D) {\n    convertToUsd(-1, \"EUR\")\n  }\n```\n\n### Accumulating Errors\n\nWhat if we want to accumulate more than one error in a dedicated data structure? For example, say we have a list of ids,\nand we want to retrieve all the associated users.\n\n```scala 3\ndef findUsersByIds(ids: List[String]): List[User] raises List[UserNotFound]\n```\n\nAs you might guess, the library gives us a dedicated function to execute a transformation on a list of values and\naccumulate the errors in a List[Error]. The function is called `mapOrAccumulate`:\n\n```scala 3\ndef findUsersByIds(ids: List[String]): List[User] raises List[UserNotFound] =\n  Raise.mapOrAccumulate(ids) { id =\u003e\n    findUserById(id)\n  }\n```\n\nIf at least one error is raised, the `mapOrAccumulate` function will accumulate all the errors in a List[Error]. If no\nerror is raised, the function will return a List[User]. There is also a version of the `mapOrAccumulate` function\ndefined as extension method of any `Iterable[A]` type:\n\n```scala 3\nimport in.rcard.raise4s.RaiseIterableDef.mapOrAccumulate\n\ndef findUsersByIds(ids: List[String]): List[User] raises List[UserNotFound] =\n  ids.mapOrAccumulate { id =\u003e\n    findUserById(id)\n  }\n```\n\nWe can obtain the same result using the `values` extension function:\n\n```scala 3\nimport in.rcard.raise4s.RaiseIterableDef.values\n\ndef findUsersByIds(ids: List[String]): List[User] raises List[UserNotFound] =\n  val usersOrErrors: List[User raises UserNotFound] = ids.map(id =\u003e findUserById(id))\n  usersOrErrors.values\n```\n\nDid anyone say `traverse`?\n\n### Zipping Errors\n\nAs we said, the `mapOrAccumulate` function allows the combination of the results of a transformation applied to a\ncollection of elements of the same type. What if we want to combine transformations applied to objects of different\ntypes?\n\nA classic example is the validation during the creation of an object. Say we want a `Salary` type with amount and\ncurrency information:\n\n```scala 3\ncase class Salary(amount: Double, currency: String)\n```\n\nNow, we need to create a hierarchy of the possible logical typed errors we can have while creating a Salary object.\nWe’ll check for the following two errors:\n\n1. The amount must be greater than zero\n2. The currency must be made of three capital letters\n\nWe define the following hierarchy of types to represent the above errors:\n\n```scala 3\nsealed trait SalaryError\n\ncase object NegativeAmount extends SalaryError\n\ncase class InvalidCurrency(message: String) extends SalaryError\n```\n\nIn general, we want to avoid the creation of invalid objects. To do so, we can define what we call a smart constructor.\nSmart constructors are factories that look like regular constructors but perform validations and generally return the\nvalid object or some typed error. The smart constructor must perform all the needed validation on input data before\ncreating a concrete instance of the object.\n\nWe can’t use the `mapOrAccumulate` function we previously saw because we don’t have a list of objects of the same type\nas input. Fortunately, the library provides the `zipOrAccumulate` function, which we need.\n\n```scala 3\nobject Salary {\n  def apply(amount: Double, currency: String): Salary raises List[SalaryError] = {\n    Raise.zipOrAccumulate(\n      {\n        Raise.ensure(amount \u003e= 0.0)(NegativeAmount)\n      },\n      {\n        Raise.ensure(currency != null \u0026\u0026 currency.matches(\"[A-Z]{3}\")) {\n          InvalidCurrency(\"Currency must be not empty and valid\")\n        }\n      }\n    ) { (_, _) =\u003e\n      Salary(amount, currency)\n    }\n  }\n}\n```\n\nMany different versions of the function differ in the number of input parameters. The maximum number of single input\nparameters is 9. If we need more, we must apply the function recursively multiple times.\n\n### The New `accumulate` DSL\n\nRecently, we added an experimental DSL for error accumulation inspired by the [new Arrow 2.0 library](https://arrow-kt.io/community/blog/2024/12/05/arrow-2-0/#simple-accumulation-in-raise). We decided to try a more user-friendly DSL instead of the exoteric `mapOrAccumulate` and `zipAccumulate` functions, called `accumulate`:\n\n```scala 3\nobject Salary {\n  def apply(amount: Double, currency: String): Salary raises List[SalaryError] = \n    accumulate {\n      val validatedAmount = accumulating {\n        ensure[SalaryError](amount \u003e= 0.0)(NegativeAmount)\n        amount\n      }\n      val validatedCurrency = accumulating {\n        ensure[SalaryError](currency != null \u0026\u0026 currency.matches(\"[A-Z]{3}\")) {\n          InvalidCurrency(\"Currency must be not empty and valid\")\n        }\n        currency\n      }\n      Salary(validatedAmount, validatedCurrency)\n    }\n}\n```\n\nThe `accumulate` and `accumulating` functions are defined in the `in.rcard.raise4s.Accumulation` object. As you can see, no more lambda tuples are needed. Every raised error is intercepted and accumulated in a list of errors inside the `accumulating` block. The `accumulate` function will return the happy path or a list of errors.\n\nThe `accumulating` function returns an instance of the `Value[A]` type and not an instance of the validated `A` object itself. The first time the `Value[A]` instance is used, the library performs an implicit conversion to the `A` object under the hood. The implicit conversion will raise the accumulated errors if there is an error during the validation.\n\nWe can also use the `accumulate` API to mimic the behavior of the `mapOrAccumulate` function. For example, we can rewrite the `findUsersByIds` function as follows:\n\n```scala 3\nimport in.rcard.raise4s.Accumulation.*\n\ndef findUsersByIds(ids: List[String]): List[User] raises List[Error] = accumulate {\n  val users = ids.map(id =\u003e accumulating { findUserById(id) })\n  users\n}\n```\n\nThe above code uses another implicit conversion under the hood. It's converting from a `List[Value[Error, User]]` to a `List[User] raises List[Error]`. In case you want to remove the extra `users` variable, you need to help the compiler understand the return type of the `map` function:\n\n```scala 3\ndef findUsersByIds(ids: List[String]): List[User] raises List[Error] = accumulate {\n  ids.map[Value[Error, User]](id =\u003e accumulating { findUserById(id) })\n}\n```\n\nIf we don't force the `map` function to return a `Value[Error, User]` instance, the compiler will infer the return type as `List[User]` and the implicit conversion for the single `Value[Error, User]` will be applied, instead of the one for the `List[Value[Error, User]]`.\n\n### Conversion to Wrapped Types\n\nWhat if we want to convert a computation in the `Raise[E]` context to a function returning an `Either[E, A]`,\na `Try[A]`, an `Option[A]`? Well, nothing is more straightforward than that.\n\nLet’s start with `Either[E, A]`. The `either` builder is what we're searching for. We can translate the result of\nthe `findUserById` function to an `Either[Error, User]` quite easily:\n\n```scala 3\nval maybeUser: Either[Error, User] =\n  Raise.either:\n    findUserById(\"42\")\n```\n\nIf we want to retrieve more information of a user using her name, we can just use the `User` instance directly:\n\n```scala 3\nval maybeUserNameInUpperCase: Either[Error, String] =\n  Raise.either:\n    val user: User = findUserById(\"42\")\n    user.name.toUpperCase\n```\n\nPlease praise the simplicity and absence of boilerplate code, like calls to `map` functions or when expressions. This is\nthe power of Scala direct style.\n\nIt’s also possible to make the backward conversion from an `Either[E, A]` to a `Raise[E]` using the `value` function:\n\n```scala 3\nval userNameInUpperCaseRaiseLambda: Raise[Error] ?=\u003e String = maybeUserNameInUpperCase.value\n```\n\nThe `value` function is very handful when we need to compose functions that return an `Either[E, A]`:\n\n```scala 3\nval one = Right(1)\nval two = Right(2)\nval three = Raise.either {\n  val oneValue = one.value\n  val twoValue = two.value\n  oneValue + twoValue\n}\n```\n\nThe `value` function calls the `raise` function if the `Either` instance is a `Left`; otherwise, it returns the value\nwrapped by the `Right` instance. Despite the trivial logic implemented in the above example, it's a good example of how\nto compose functions that return an `Either[E, A]` using the Raise DSL without the use of any `flatMap` function.\n\nA useful shortcut is available when we need to transform a `List[Either[E, A]]` into a `List[A] raises E`. The eventual\nraised error `E` is the first error found in the list of `Either[E, A]`:\n\n```scala 3\nval eitherList: List[Either[String, Int]] = List(Right(1), Left(\"error\"), Right(3))\nval raiseList: List[Int] raises String = listWithError.value\n```\n\nBe aware that before version 0.0.5, the `value` function was called `bind()`.\n\nWe can do the same with `Try[A]` and `Option[A]` using the `asTry` and `option` builders, respectively. Let's start with\nthe `asTry` builder. In this case, the only available type of error is `Throwable`:\n\n```scala 3\nval maybeUserWithTry: Try[User] =\n  Raise.asTry:\n    findUserByIdWithEx(\"42\")\n```\n\nAs you might guess, any fatal exception thrown inside the `asTry` context will bubble up and not handled.\n\nLast but not least, the `option` builder:\n\n```scala 3\ndef findUserByIdWithNone(id: String): User raises None.type =\n  if (id == \"42\") User(id, \"Alice\") else Raise.raise(None)\n\nval maybeUserWithOpt: Option[User] =\n  Raise.option:\n    findUserByIdWithNone(\"42\")\n```\n\nThe `bind` function is available for `Try[A]` and `Option[A]` as well.\n\nBy the way, there are more feature in the Raise DSL. Please, check the documentation for more information.\n\n## Strategies\n\nWe can call a _strategy_ the way we want to handle the errors raised by a function. The whole `raise4s` library give you\nmany predefined ways to deal with errors. For example, you can `fold` on a function, `run` it, `either` it, `asTry`\nit, `option` it, and so on.\n\nHowever, the library gives you the possibility to define your own strategy. To do so, you need to define a new instance\nof the `Raise[E]` type class. The `Raise[E]` type class is a type class that defines how to handle errors of type `E`.\nThe `Raise[E]` type class is defined as follows:\n\n```scala 3\ntrait Raise[-Error]:\n  def raise(e: Error): Nothing\n```\n\nFor example, you can define a strategy that simply throw an exception for every error raised by a function:\n\n```scala 3\ndef loadConfiguration(file: String): Configuration raises ConfigurationError = ???\n\ngiven Raise[Any] = error =\u003e throw new RuntimeException(error.toString)\nval configuration = loadConfiguration(args(0))\n```\n\nThe library gives you a type alias to define strategies on all the errors:\n\n```scala 3\ntype anyRaised = Raise[Any]\n```\n\nThen, we can rewrite the above example as follows:\n\n```scala 3\ngiven anyRaised = error =\u003e throw new RuntimeException(error.toString)\n```\n\nWe can define a strategy for a single error as well:\n\n```scala 3\ngiven Raise[ConfigurationError] = error =\u003e exit(1)\n```\n\nIn the above example we're saying that a configuration error must stop the execution of the program.\n\n### `MapError` Strategy\n\nThe library defines a `withError` function to map an error raised by a function to an error of a different type:\n\n```scala 3\nval actual = either {\n  Raise.withError[Int, String, Int](s =\u003e s.length) {\n    raise(\"error\")\n  }\n}\nactual should be(Left(5))\n```\n\nIn the above example, we map the error of type `String` raised by the given lambda into an error of type `Int`.\nThe `withError` function does its job quite well. However, the function's ergonomics are not so good due to the pair of\nlambdas we need to provide: The first one is the function that maps the error, and the second one is the function that\nraises the error.\n\nWe can achieve the same result using a dedicated strategy. In detail, the library defines a `MapError` strategy that\nmaps an error of type `E` into an error of type `F`. The `MapError` strategy is defined as follows:\n\n```scala 3\ntrait MapError[From, To] extends Raise[From] {\n  def map(error: From): To\n\n  def raise(error: From): Nothing = throw Raised(map(error))\n}\n```\n\nThen, we can map an error of type `String` into an error of type `Int` by implementing the `MapError` strategy as\nfollows:\n\n```scala 3\nval finalLambda: String raises Int = {\n  given MapError[String, Int] = error =\u003e error.length\n\n  raise(\"Oops!\")\n}\nval result: Int | String = Raise.run(finalLambda)\nresult shouldBe 5\n```\n\nAs you can see, we're entirely focused on the happy path, and we can define how to handle the errors in a dedicated\nplace.\n\n### `RecoverWith` Strategy\n\nWe've already seen how to recover from a typed error using the `Raise.recover` function. The function works quite well,\nbut it's not so ergonomic. We need to provide two lambdas as input for the function. The first is the block we want to\nexecute, and the second is the lambda to apply in case of recovery.\n\nWe can obtain the same result by applying the `RecoverWith` strategy and by using the `recoverable` DSL:\n\n```scala 3\ncase class NegativeAmount(amount: Double) extends Error\n\ndef convertToUsd(amount: Double, currency: String): Double raises NegativeAmount =\n  if (amount \u003c 0) Raise.raise(NegativeAmount(amount))\n  else amount * 1.2\n\ngiven RecoverWith[NegativeAmount, Double] = {\n  case NegativeAmount =\u003e 0.0D\n}\nval usdAmount: Double = Raise.recoverable {\n  convertToUsd(-1, \"EUR\")\n}\n```\n\nThe strategy lets us define how to recover from a typed error in a dedicated place, leaving the happy logic in the main\nblock.\n\n### Tracing\n\nWhen a logical error is raised, the library will not print any information about the error by default. Sometimes, it's useful for debugging purposes to have a trace of the error. The library allows you to define a strategy called `TraceWith` that processes the error together with a stack trace that identifies where the error was raised.\n\nTo enable tracing for a specific code block, wrap it inside the `trace` DSL and provide a `TraceWith` strategy:\n\n```scala 3\nimport in.rcard.raise4s.Raise.{raise, traced}\nimport in.rcard.raise4s.Strategies.TraceWith\n\ngiven TraceWith[String] = (trace: Traced) =\u003e {\n  trace.printStackTrace()\n}\nval lambda: Int raises String = traced {\n  raise(\"Oops!\")\n}\nval actual: String | Int = Raise.run(lambda)\nactual shouldBe \"Oops!\"\n```\n\nThe `Traced` exception the tracing engine uses contains the original typed error.\n\n## Contributing\n\nIf you want to contribute to the project, please do it! Any help is welcome.\n\n## Acknowledgments\n\nThis project is inspired by the Arrow Kt library's Raise DSL. I want to thank the Arrow Kt team for their outstanding\nwork. In detail, thanks to Simon Vergauwen for the great discussions we had on Slack. A lot of thanks also to Daniel\nCiocîrlan, my mentor and friend.\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcardin%2Fraise4s","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frcardin%2Fraise4s","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcardin%2Fraise4s/lists"}