Skip to content

Commit

Permalink
Mention forEachAccumulating (#274)
Browse files Browse the repository at this point in the history
  • Loading branch information
serras authored Feb 28, 2024
1 parent f9534da commit ab7bab5
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 48 deletions.
29 changes: 25 additions & 4 deletions content/docs/learn/typed-errors/working-with-typed-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,27 @@ fun example() {
<!--- KNIT example-typed-errors-16.kt -->
<!--- TEST assert -->

:::tip Accumulating errors but not values

If you need to execute a computation that may `raise` errors over all the elements of an iterable or sequence, but without storing the resulting values, `forEachAccumulating` is your tool of choice. The relation between `mapOrAccumulate` and `forEachAccumulating` is similar to that of `map` and `forEach` in Kotlin's standard library.

<!--- INCLUDE
import arrow.core.raise.either
import arrow.core.raise.ensure
import arrow.core.raise.forEachAccumulating
-->
```kotlin
fun example() = either {
forEachAccumulating(1 .. 10) { i ->
ensure(i % 2 == 0) { "$i is not even" }
}
}
```
<!--- KNIT example-typed-errors-17.kt -->
<!--- TEST assert -->

:::

### Accumulating different computations

In the example above we are providing one single function to operate on a sequence of elements.
Expand All @@ -722,7 +743,7 @@ As a guiding example, let's consider information about a user, where the name sh
```kotlin
data class User(val name: String, val age: Int)
```
<!--- KNIT example-typed-errors-17.kt -->
<!--- KNIT example-typed-errors-18.kt -->

It's customary to define the different problems that may arise from validation as a sealed interface:

Expand Down Expand Up @@ -762,7 +783,7 @@ fun example() {
User("", -1) shouldBe Left(UserProblem.EmptyName)
}
```
<!--- KNIT example-typed-errors-18.kt -->
<!--- KNIT example-typed-errors-19.kt -->
<!--- TEST assert -->

<!--- INCLUDE
Expand Down Expand Up @@ -805,7 +826,7 @@ fun example() {
User("", -1) shouldBe Left(nonEmptyListOf(UserProblem.EmptyName, UserProblem.NegativeAge(-1)))
}
```
<!--- KNIT example-typed-errors-19.kt -->
<!--- KNIT example-typed-errors-20.kt -->
<!--- TEST assert -->

:::tip Error accumulation and concurrency
Expand Down Expand Up @@ -842,7 +863,7 @@ fun example() {
intError shouldBe Either.Left("problem".length)
}
-->
<!--- KNIT example-typed-errors-20.kt -->
<!--- KNIT example-typed-errors-21.kt -->
<!--- TEST assert -->

A very common pattern is using `withError` to "bridge" validation errors of sub-components into validation errors of the larger value.
Expand Down
10 changes: 9 additions & 1 deletion guide/src/test/kotlin/examples/example-typed-errors-17.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
// This file was automatically generated from working-with-typed-errors.md by Knit tool. Do not edit.
package arrow.website.examples.exampleTypedErrors17

data class User(val name: String, val age: Int)
import arrow.core.raise.either
import arrow.core.raise.ensure
import arrow.core.raise.forEachAccumulating

fun example() = either {
forEachAccumulating(1 .. 10) { i ->
ensure(i % 2 == 0) { "$i is not even" }
}
}
25 changes: 1 addition & 24 deletions guide/src/test/kotlin/examples/example-typed-errors-18.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,4 @@
// This file was automatically generated from working-with-typed-errors.md by Knit tool. Do not edit.
package arrow.website.examples.exampleTypedErrors18

import arrow.core.Either
import arrow.core.Either.Left
import arrow.core.raise.either
import arrow.core.raise.ensure
import io.kotest.matchers.shouldBe

sealed interface UserProblem {
object EmptyName: UserProblem
data class NegativeAge(val age: Int): UserProblem
}

data class User private constructor(val name: String, val age: Int) {
companion object {
operator fun invoke(name: String, age: Int): Either<UserProblem, User> = either {
ensure(name.isNotEmpty()) { UserProblem.EmptyName }
ensure(age >= 0) { UserProblem.NegativeAge(age) }
User(name, age)
}
}
}

fun example() {
User("", -1) shouldBe Left(UserProblem.EmptyName)
}
data class User(val name: String, val age: Int)
14 changes: 5 additions & 9 deletions guide/src/test/kotlin/examples/example-typed-errors-19.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ package arrow.website.examples.exampleTypedErrors19

import arrow.core.Either
import arrow.core.Either.Left
import arrow.core.NonEmptyList
import arrow.core.nonEmptyListOf
import arrow.core.raise.either
import arrow.core.raise.ensure
import arrow.core.raise.zipOrAccumulate
import io.kotest.matchers.shouldBe

sealed interface UserProblem {
Expand All @@ -17,15 +14,14 @@ sealed interface UserProblem {

data class User private constructor(val name: String, val age: Int) {
companion object {
operator fun invoke(name: String, age: Int): Either<NonEmptyList<UserProblem>, User> = either {
zipOrAccumulate(
{ ensure(name.isNotEmpty()) { UserProblem.EmptyName } },
{ ensure(age >= 0) { UserProblem.NegativeAge(age) } }
) { _, _ -> User(name, age) }
operator fun invoke(name: String, age: Int): Either<UserProblem, User> = either {
ensure(name.isNotEmpty()) { UserProblem.EmptyName }
ensure(age >= 0) { UserProblem.NegativeAge(age) }
User(name, age)
}
}
}

fun example() {
User("", -1) shouldBe Left(nonEmptyListOf(UserProblem.EmptyName, UserProblem.NegativeAge(-1)))
User("", -1) shouldBe Left(UserProblem.EmptyName)
}
29 changes: 21 additions & 8 deletions guide/src/test/kotlin/examples/example-typed-errors-20.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
// This file was automatically generated from working-with-typed-errors.md by Knit tool. Do not edit.
package arrow.website.examples.exampleTypedErrors20

import arrow.core.*
import arrow.core.raise.*
import arrow.core.Either
import arrow.core.Either.Left
import arrow.core.NonEmptyList
import arrow.core.nonEmptyListOf
import arrow.core.raise.either
import arrow.core.raise.ensure
import arrow.core.raise.zipOrAccumulate
import io.kotest.matchers.shouldBe

val stringError: Either<String, Boolean> = "problem".left()
sealed interface UserProblem {
object EmptyName: UserProblem
data class NegativeAge(val age: Int): UserProblem
}

val intError: Either<Int, Boolean> = either {
// transform error String -> Int
withError({ it.length }) {
stringError.bind()
data class User private constructor(val name: String, val age: Int) {
companion object {
operator fun invoke(name: String, age: Int): Either<NonEmptyList<UserProblem>, User> = either {
zipOrAccumulate(
{ ensure(name.isNotEmpty()) { UserProblem.EmptyName } },
{ ensure(age >= 0) { UserProblem.NegativeAge(age) } }
) { _, _ -> User(name, age) }
}
}
}

fun example() {
intError shouldBe Either.Left("problem".length)
User("", -1) shouldBe Left(nonEmptyListOf(UserProblem.EmptyName, UserProblem.NegativeAge(-1)))
}
18 changes: 18 additions & 0 deletions guide/src/test/kotlin/examples/example-typed-errors-21.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// This file was automatically generated from working-with-typed-errors.md by Knit tool. Do not edit.
package arrow.website.examples.exampleTypedErrors21

import arrow.core.*
import arrow.core.raise.*
import io.kotest.matchers.shouldBe

val stringError: Either<String, Boolean> = "problem".left()

val intError: Either<Int, Boolean> = either {
// transform error String -> Int
withError({ it.length }) {
stringError.bind()
}
}
fun example() {
intError shouldBe Either.Left("problem".length)
}
8 changes: 6 additions & 2 deletions guide/src/test/kotlin/examples/test/TypedErrorsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ class TypedErrorsTest {
arrow.website.examples.exampleTypedErrors16.example()
}

@Test fun ExampleTypedErrors18() = runTest {
arrow.website.examples.exampleTypedErrors18.example()
@Test fun ExampleTypedErrors17() = runTest {
arrow.website.examples.exampleTypedErrors17.example()
}

@Test fun ExampleTypedErrors19() = runTest {
Expand All @@ -51,4 +51,8 @@ class TypedErrorsTest {
arrow.website.examples.exampleTypedErrors20.example()
}

@Test fun ExampleTypedErrors21() = runTest {
arrow.website.examples.exampleTypedErrors21.example()
}

}

0 comments on commit ab7bab5

Please sign in to comment.