Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mention forEachAccumulating #274

Merged
merged 2 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
}

}