Repository: rcardin/raise4s (scala)

Issue:
Add a `values` extension function for `Iterable[Raise[Error] ?=> A]`
Add a `values` extension function for `Iterable[Raise[Error] ?=> A]`. The function must behave like the following:

```scala 3
val intermediateResults: List[Raise[String] ?=> Int] = List(1, 2, 3, 4).map(i => raise(i.toString))
val results: RaiseAcc[String] ?=> List[Int] = intermediateResults.values
```

Pull request:
Closes #96

Patch:
diff --git a/README.md b/README.md
index 3b5223d..9200046 100644
--- a/README.md
+++ b/README.md
@@ -210,12 +210,26 @@ error is raised, the function will return a List[User]. There is also a version
 defined as extension method of any `Iterable[A]` type:
 
 ```scala 3
+import in.rcard.raise4s.RaiseIterableDef.mapOrAccumulate
+
 def findUsersByIds(ids: List[String]): List[User] raises List[UserNotFound] =
   ids.mapOrAccumulate { id =>
     findUserById(id)
   }
 ```
 
+We can obtain the same result using the `values` extension function:
+
+```scala 3
+import in.rcard.raise4s.RaiseIterableDef.values
+
+def findUsersByIds(ids: List[String]): List[User] raises List[UserNotFound] =
+  val usersOrErrors: List[User raises UserNotFound] = ids.map(id => findUserById(id))
+  usersOrErrors.values
+```
+
+Did anyone say `traverse`?
+
 ### Zipping Errors
 
 As we said, the `mapOrAccumulate` function allows the combination of the results of a transformation applied to a
diff --git a/core/src/main/scala/in/rcard/raise4s/Fold.scala b/core/src/main/scala/in/rcard/raise4s/Fold.scala
index 1417828..da89a20 100644
--- a/core/src/main/scala/in/rcard/raise4s/Fold.scala
+++ b/core/src/main/scala/in/rcard/raise4s/Fold.scala
@@ -1,5 +1,7 @@
 package in.rcard.raise4s
 
+import in.rcard.raise4s.Raise.mapOrAccumulate
+
 private[raise4s] inline def _mapOrAccumulate[Error, A, B](iterable: Iterable[A])(
     inline transform: Raise[Error] ?=> A => B
 )(using r: RaiseAcc[Error]): List[B] =
@@ -47,9 +49,42 @@ object RaiseIterableDef:
       * @return
       *   A list of the transformed elements of the iterable
       */
-    inline def mapOrAccumulate(inline transform: Raise[Error] ?=> A => B)(using r: RaiseAcc[Error]): List[B] =
+    inline def mapOrAccumulate(inline transform: Raise[Error] ?=> A => B)(using
+        r: RaiseAcc[Error]
+    ): List[B] =
       Raise.mapOrAccumulate(iterable)(transform)
 
+  extension [Error, A](iterable: Iterable[Raise[Error] ?=> A]) {
+
+    /** Accumulates the errors of executions of the elements of the iterable and returns a list of
+      * the values or the accumulated errors.
+     * 
+     * <h2>Example</h2>
+     * {{{
+     * val iterableWithInnerRaise: List[Int raises String] = List(1, 2, 3, 4, 5).map(value =>
+     *   if (value % 2 == 0) {
+     *     Raise.raise(value.toString)
+     *   } else {
+     *     value
+     *   }
+     * )
+     * val iterableWithRaiseAcc: List[Int] raises List[String] = iterableWithInnerRaise.values
+     * val actual = Raise.fold(
+     *   iterableWithRaiseAcc,
+     *   identity,
+     *   identity
+     * )
+     * actual shouldBe List("2", "4")
+     * }}}
+      *
+      * @return
+      *   The list of the values or the accumulated errors
+      */
+    inline def values: RaiseAcc[Error] ?=> List[A] = {
+      Raise.mapOrAccumulate(iterable)(identity)
+    }
+  }
+
 private[raise4s] inline def _zipOrAccumulate[Error, A, B, C, D, E, F, G, H, I, J](
     inline action1: Raise[Error] ?=> A,
     inline action2: Raise[Error] ?=> B,


Test patch:
diff --git a/core/src/test/scala/in/rcard/raise4s/FoldSpec.scala b/core/src/test/scala/in/rcard/raise4s/FoldSpec.scala
index bac4aef..8feed98 100644
--- a/core/src/test/scala/in/rcard/raise4s/FoldSpec.scala
+++ b/core/src/test/scala/in/rcard/raise4s/FoldSpec.scala
@@ -1,6 +1,7 @@
 package in.rcard.raise4s
 
 import in.rcard.raise4s.RaiseIterableDef.mapOrAccumulate
+import in.rcard.raise4s.RaiseIterableDef.values
 import org.scalatest.flatspec.AnyFlatSpec
 import org.scalatest.matchers.should.Matchers
 
@@ -115,6 +116,39 @@ class FoldSpec extends AnyFlatSpec with Matchers {
     actual shouldBe List("2", "4")
   }
 
+  "Values extension method" should "extract the values from the iterable" in {
+    val iterableWithInnerRaise: List[Int raises String] = List(1, 2, 3, 4, 5)
+    val iterableWithRaiseAcc: List[Int] raises List[String] = iterableWithInnerRaise.values
+
+    val actual = Raise.fold(
+      iterableWithRaiseAcc,
+      error => fail(s"An error occurred: $error"),
+      identity
+    )
+
+    actual shouldBe List(1, 2, 3, 4, 5)
+  }
+
+  it should "accumulate all the errors" in {
+    val iterableWithInnerRaise: List[Int raises String] = List(1, 2, 3, 4, 5).map(value =>
+      if (value % 2 == 0) {
+        Raise.raise(value.toString)
+      } else {
+        value
+      }
+    )
+
+    val iterableWithRaiseAcc: List[Int] raises List[String] = iterableWithInnerRaise.values
+
+    val actual = Raise.fold(
+      iterableWithRaiseAcc,
+      identity,
+      identity
+    )
+
+    actual shouldBe List("2", "4")
+  }
+
   case class MyError2(errors: List[String])
   def combineErrors(error1: MyError2, error2: MyError2): MyError2 =
     MyError2(error1.errors ++ error2.errors)