From 982bbd335be5f7017d5068a45b034d1e4e05f5f2 Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Date: Thu, 8 Feb 2024 21:13:55 +0100 Subject: [PATCH] Mention forEachAccumulating --- .../typed-errors/working-with-typed-errors.md | 24 ++++++++++++--- .../examples/example-typed-errors-17.kt | 6 +++- .../examples/example-typed-errors-18.kt | 25 +--------------- .../examples/example-typed-errors-19.kt | 14 ++++----- .../examples/example-typed-errors-20.kt | 29 ++++++++++++++----- .../examples/example-typed-errors-21.kt | 18 ++++++++++++ .../kotlin/examples/test/TypedErrorsTest.kt | 8 +++-- 7 files changed, 76 insertions(+), 48 deletions(-) create mode 100644 guide/src/test/kotlin/examples/example-typed-errors-21.kt diff --git a/content/docs/learn/typed-errors/working-with-typed-errors.md b/content/docs/learn/typed-errors/working-with-typed-errors.md index 05e778fa..9562b0d8 100644 --- a/content/docs/learn/typed-errors/working-with-typed-errors.md +++ b/content/docs/learn/typed-errors/working-with-typed-errors.md @@ -711,6 +711,22 @@ fun example() { +:::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. + +```kotlin +fun example() { + (1..10).forEachAccumulating { i -> + ensure(i % 2 == 0) { "$i is not even" } + } +} +``` + + + +::: + ### Accumulating different computations In the example above we are providing one single function to operate on a sequence of elements. @@ -722,7 +738,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) ``` - + It's customary to define the different problems that may arise from validation as a sealed interface: @@ -762,7 +778,7 @@ fun example() { User("", -1) shouldBe Left(UserProblem.EmptyName) } ``` - + + :::tip Error accumulation and concurrency @@ -842,7 +858,7 @@ fun example() { intError shouldBe Either.Left("problem".length) } --> - + A very common pattern is using `withError` to "bridge" validation errors of sub-components into validation errors of the larger value. diff --git a/guide/src/test/kotlin/examples/example-typed-errors-17.kt b/guide/src/test/kotlin/examples/example-typed-errors-17.kt index ce3eb3fd..fdf81b5c 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-17.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-17.kt @@ -1,4 +1,8 @@ // 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) +fun example() { + (1..10).forEachAccumulating { i -> + ensure(i % 2 == 0) { "$i is not even" } + } +} diff --git a/guide/src/test/kotlin/examples/example-typed-errors-18.kt b/guide/src/test/kotlin/examples/example-typed-errors-18.kt index adfd3522..70edca90 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-18.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-18.kt @@ -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 = 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) diff --git a/guide/src/test/kotlin/examples/example-typed-errors-19.kt b/guide/src/test/kotlin/examples/example-typed-errors-19.kt index a9ecad21..a66bb4db 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-19.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-19.kt @@ -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 { @@ -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, 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 = 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) } diff --git a/guide/src/test/kotlin/examples/example-typed-errors-20.kt b/guide/src/test/kotlin/examples/example-typed-errors-20.kt index 953d7f3e..fb713e26 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-20.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-20.kt @@ -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 = "problem".left() +sealed interface UserProblem { + object EmptyName: UserProblem + data class NegativeAge(val age: Int): UserProblem +} -val intError: Either = 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, 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))) } diff --git a/guide/src/test/kotlin/examples/example-typed-errors-21.kt b/guide/src/test/kotlin/examples/example-typed-errors-21.kt new file mode 100644 index 00000000..4f4cdcec --- /dev/null +++ b/guide/src/test/kotlin/examples/example-typed-errors-21.kt @@ -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 = "problem".left() + +val intError: Either = either { + // transform error String -> Int + withError({ it.length }) { + stringError.bind() + } +} +fun example() { + intError shouldBe Either.Left("problem".length) +} diff --git a/guide/src/test/kotlin/examples/test/TypedErrorsTest.kt b/guide/src/test/kotlin/examples/test/TypedErrorsTest.kt index aa0930c7..c9167b17 100644 --- a/guide/src/test/kotlin/examples/test/TypedErrorsTest.kt +++ b/guide/src/test/kotlin/examples/test/TypedErrorsTest.kt @@ -38,8 +38,8 @@ class TypedErrorsTest : StringSpec({ arrow.website.examples.exampleTypedErrors16.example() } - "ExampleTypedErrors18" { - arrow.website.examples.exampleTypedErrors18.example() + "ExampleTypedErrors17" { + arrow.website.examples.exampleTypedErrors17.example() } "ExampleTypedErrors19" { @@ -50,6 +50,10 @@ class TypedErrorsTest : StringSpec({ arrow.website.examples.exampleTypedErrors20.example() } + "ExampleTypedErrors21" { + arrow.website.examples.exampleTypedErrors21.example() + } + }) { override fun timeout(): Long = 1000 }