Repository: rcardin/raise4s (scala)

Issue:
Add a `combineErrorsS` function for `Iterable[Raise[Error] ?=> A]` type that uses a `Semigroup[Error]` instance
Add a `combineErrorsS` function for `Iterable[Raise[Error] ?=> A]` type that uses a `Semigroup[Error]` instance. The function must behave like the following:

```scala 3
import cats.Semigroup

implicit val intAdditionSemigroup: Semigroup[String] = _ + _

val intermediateResults: List[Raise[String] ?=> Int] = List(1, 2, 3, 4).map(i => raise(i.toString))
val results: Raise[String] ?=> List[Int] = intermediateResults.combineErrorsS
```

Pull request:
Closes #99

Patch:
diff --git a/cats-raise4s/README.md b/cats-raise4s/README.md
index 765e55c..3490ea4 100644
--- a/cats-raise4s/README.md
+++ b/cats-raise4s/README.md
@@ -128,6 +128,32 @@ In general, the integration lets you use the _Cats_ type classes with the _Raise
   actual shouldBe NonEmptyList.of("2", "4")
   ```
 
+- Use the `combineErrorsS` with a `Semigroup` instance to accumulate errors into a `E` from a `Iterable[A raises E]` block.
+
+  ```scala 3
+  import cats.data.*
+  import in.rcard.raise4s.cats.CatsBind.combineErrorsS
+
+  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 iterableWithOuterRaise: List[Int] raises String = iterableWithInnerRaise.combineErrorsS
+  
+  val actual = Raise.fold(
+    iterableWithOuterRaise,
+    identity,
+    identity
+  )
+  
+  actual shouldBe "24"
+  ```
+
 - Use the `Semigroup` type class to combine errors in the `zipOrAccumlateS` set of functions.
 
   ```scala 3
diff --git a/cats-raise4s/src/main/scala/in/rcard/raise4s/cats/CatsBind.scala b/cats-raise4s/src/main/scala/in/rcard/raise4s/cats/CatsBind.scala
index f82b3e2..2aa65a6 100644
--- a/cats-raise4s/src/main/scala/in/rcard/raise4s/cats/CatsBind.scala
+++ b/cats-raise4s/src/main/scala/in/rcard/raise4s/cats/CatsBind.scala
@@ -1,5 +1,6 @@
 package in.rcard.raise4s.cats
 
+import cats.Semigroup
 import cats.data.Validated.{Invalid, Valid}
 import cats.data.{NonEmptyList, Validated}
 import in.rcard.raise4s.Raise
@@ -40,6 +41,41 @@ object CatsBind {
       */
     inline def values: RaiseNel[Error] ?=> List[A] = CatsRaise.mapOrAccumulate(iterable)(identity)
 
+    /**
+     * Accumulates all the occurred errors using the combine operator of the implicit [[Semigroup]]  and returns 
+     * the list of the values or the accumulated errors.
+     *
+     * <h2>Example</h2>
+     * {{{
+     * import cats.data.*
+     * import in.rcard.raise4s.cats.CatsBind.combineErrorsS
+     * 
+     * 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 iterableWithOuterRaise: List[Int] raises String = iterableWithInnerRaise.combineErrorsS
+     * 
+     * val actual = Raise.fold(
+     *   iterableWithOuterRaise,
+     *   identity,
+     *   identity
+     * )
+     * 
+     * actual shouldBe "24"
+     * }}}
+     *
+     * @param semigroup The semigroup to combine the errors defined on the type `Error`
+     * @return The list of the values or the accumulated errors
+     */
+    inline def combineErrorsS(using semigroup: Semigroup[Error]): Raise[Error] ?=> List[A] =
+      CatsRaise.mapOrAccumulateS(iterable)(identity)
+
   extension [Error, A](nonEmptyList: NonEmptyList[Raise[Error] ?=> A])
     /** Accumulates the errors of executions of the elements of the non-empty list and returns a
       * non-empty list of the values or a non-empty list of the accumulated errors.
diff --git a/cats-raise4s/src/main/scala/in/rcard/raise4s/cats/CatsRaise.scala b/cats-raise4s/src/main/scala/in/rcard/raise4s/cats/CatsRaise.scala
index ca8a7fc..591f775 100644
--- a/cats-raise4s/src/main/scala/in/rcard/raise4s/cats/CatsRaise.scala
+++ b/cats-raise4s/src/main/scala/in/rcard/raise4s/cats/CatsRaise.scala
@@ -41,7 +41,7 @@ object CatsRaise {
     *   The Raise context
     * @tparam Error
     *   The type of the logical error that can be raised. It must have a [[Semigroup]] instance
-    *   available available
+    *   available
     * @tparam A
     *   The type of the elements in the `iterable`
     * @tparam B


Test patch:
diff --git a/cats-raise4s/src/test/scala/in/rcard/raise4s/cats/CatsBindSpec.scala b/cats-raise4s/src/test/scala/in/rcard/raise4s/cats/CatsBindSpec.scala
index c62e69f..5aa45f2 100644
--- a/cats-raise4s/src/test/scala/in/rcard/raise4s/cats/CatsBindSpec.scala
+++ b/cats-raise4s/src/test/scala/in/rcard/raise4s/cats/CatsBindSpec.scala
@@ -1,8 +1,8 @@
 package in.rcard.raise4s.cats
 
 import cats.data.*
-import in.rcard.raise4s.cats.CatsBind.{value, values}
-import in.rcard.raise4s.{Raise, raises}
+import in.rcard.raise4s.cats.CatsBind.{value, values, combineErrorsS}
+import in.rcard.raise4s.{Raise, raises, Raise as value}
 import org.scalatest.flatspec.AnyFlatSpec
 import org.scalatest.matchers.should.Matchers
 
@@ -121,4 +121,42 @@ class CatsBindSpec extends AnyFlatSpec with Matchers {
 
     actual shouldBe NonEmptyList.of("2", "4")
   }
+
+  "combineErrorS on Semigroup[Error]" should "map all the element of the iterable" in {
+    val iterableWithInnerRaise: List[Int raises String] =
+      List(1, 2, 3, 4, 5).map { value1 =>
+        value1 + 1
+      }
+
+    val iterableWithOuterRaise: List[Int] raises String = iterableWithInnerRaise.combineErrorsS
+
+    val actual = Raise.fold(
+      iterableWithOuterRaise,
+      error => fail(s"An error occurred: $error"),
+      identity
+    )
+
+    actual shouldBe List(2, 3, 4, 5, 6)
+  }
+
+  it should "accumulate all the errors using the combine function" 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 iterableWithOuterRaise: List[Int] raises String = iterableWithInnerRaise.combineErrorsS
+
+    val actual = Raise.fold(
+      iterableWithOuterRaise,
+      identity,
+      identity
+    )
+
+    actual shouldBe "24"
+  }
 }