Repository: rcardin/raise4s (scala)

Issue:
Add a `withDefault` method to recover a `Raise[E] ?=> A` function with a value of type `A`
Add a `withDefault` method to recover a `Raise[E] ?=> A` function with a value of type `A`.
For example, here is an application:

```scala 3
def answerToAll(question: String): Int = withDefault(42) {
  raise("I don't know")
}
```

Pull request description:
Closes #82

Patch:
diff --git a/README.md b/README.md
index e5e0bcb..1bd100f 100644
--- a/README.md
+++ b/README.md
@@ -177,6 +177,15 @@ val usdAmount: Double =
   }) { case NegativeAmount(amount) => 0.0D }
 ```
 
+If 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:
+
+```scala 3
+val usdAmount: Double =
+  Raise.withDefault(0.0D) {
+    convertToUsd(-1, "EUR")
+  }
+```
+
 ### Accumulating Errors
 
 What if we want to accumulate more than one error in a dedicated data structure? For example, say we have a list of ids
diff --git a/core/src/main/scala/in/rcard/raise4s/Raise.scala b/core/src/main/scala/in/rcard/raise4s/Raise.scala
index 37ff4d1..0d3e10d 100644
--- a/core/src/main/scala/in/rcard/raise4s/Raise.scala
+++ b/core/src/main/scala/in/rcard/raise4s/Raise.scala
@@ -1713,29 +1713,59 @@ object Raise {
   inline def run[Error, A](inline block: Raise[Error] ?=> A): Error | A =
     raise4s.Runtime._run(block)
 
-  /**
-   * Execute a block of code that can raise a logical error and return the result or tries to recover from the error
-   * using the provided recover block.
-   * 
-   * <h2>Example</h2>
-   * {{{
-   * given RecoverWith[String, Int] = error => 43
-   * val actual = Raise.recoverable {
-   *   Raise.raise("error")
-   * }
-   * actual should be(43)
-   * }}}
-   * 
-   * @param block The block of code to execute that can raise an a logical type error
-   * @param recoverBlock The block of code to execute to recover from the error
-   * @tparam Error The type of the logical error that can be raised by the `block` lambda
-   * @tparam A The type of the result of the execution of `block` lambda
-   * @return The result of the execution of the `block` lambda or the result of the recover block
-   * 
-   * @see [[RecoverWith]]
-   */
+  /** Execute a block of code that can raise a logical error and return the result or tries to
+    * recover from the error using the provided recover block.
+    *
+    * <h2>Example</h2>
+    * {{{
+    * given RecoverWith[String, Int] = error => 43
+    * val actual = Raise.recoverable {
+    *   Raise.raise("error")
+    * }
+    * actual should be(43)
+    * }}}
+    *
+    * @param block
+    *   The block of code to execute that can raise an a logical type error
+    * @param recoverBlock
+    *   The block of code to execute to recover from the error
+    * @tparam Error
+    *   The type of the logical error that can be raised by the `block` lambda
+    * @tparam A
+    *   The type of the result of the execution of `block` lambda
+    * @return
+    *   The result of the execution of the `block` lambda or the result of the recover block
+    *
+    * @see
+    *   [[RecoverWith]]
+    */
   inline def recoverable[Error, A](inline block: Raise[Error] ?=> A)(using
       inline recoverBlock: RecoverWith[Error, A]
   ): A =
     Raise.recover(block)(error => recoverBlock.recover(error))
+
+  /** Execute a `block` of code that can raise an error and return the result or the given `default`
+    * value if an error is raised.
+    *
+    * <h2>Example</h2>
+    * {{{
+    * val actual = Raise.either {
+    *   Raise.withDefault(43) { Raise.raise("error") }
+    * }
+    * actual should be(Right(43))
+    * }}}
+    *
+    * @param default
+    *   The default value to return if an error is raised
+    * @param block
+    *   The block of code to execute that can raise an error
+    * @tparam Error
+    *   The type of the logical error that can be raised by the `block` lambda
+    * @tparam A
+    *   The type of the result of the execution of `block` lambda
+    * @return
+    *   The result of the execution of the `block` lambda or the `default` value
+    */
+  inline def withDefault[Error, A](default: A)(inline block: Raise[Error] ?=> A): A =
+    Raise.recover(block)(error => default)
 }


Test patch:
diff --git a/core/src/test/scala/in/rcard/raise4s/RaiseSpec.scala b/core/src/test/scala/in/rcard/raise4s/RaiseSpec.scala
index 521198a..98a29b8 100644
--- a/core/src/test/scala/in/rcard/raise4s/RaiseSpec.scala
+++ b/core/src/test/scala/in/rcard/raise4s/RaiseSpec.scala
@@ -175,4 +175,20 @@ class RaiseSpec extends AnyFlatSpec with Matchers {
       }
     }
   }
+
+  "withDefault" should "return the value if it is not an error" in {
+    val actual = Raise.either {
+      Raise.withDefault(43) { 42 }
+    }
+
+    actual should be(Right(42))
+  }
+
+  it should "return the default value if the value is an error" in {
+    val actual = Raise.either {
+      Raise.withDefault(43) { Raise.raise("error") }
+    }
+
+    actual should be(Right(43))
+  }
 }