From 85651502afda21e390bcf912daa74a22dd26eaff Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Date: Wed, 27 Nov 2024 21:44:46 +0100 Subject: [PATCH] Remove or warn mentions of context receivers --- content/docs/learn/design/effects-contexts.md | 8 +-- .../docs/learn/design/receivers-flatmap.md | 2 +- .../learn/typed-errors/own-error-types.md | 6 +- .../typed-errors/working-with-typed-errors.md | 55 ++++++++----------- gradle/libs.versions.toml | 2 +- guide/build.gradle.kts | 4 -- .../examples/example-typed-errors-04.kt | 30 ++++++++-- .../examples/example-typed-errors-05.kt | 27 ++++----- .../examples/example-typed-errors-06.kt | 12 +--- .../examples/example-typed-errors-07.kt | 12 ++-- .../examples/example-typed-errors-08.kt | 12 ++-- .../examples/example-typed-errors-09.kt | 24 ++++---- .../examples/example-typed-errors-10.kt | 39 +++++++++---- .../examples/example-typed-errors-11.kt | 27 +++++---- .../examples/example-typed-errors-12.kt | 28 ++-------- .../examples/example-typed-errors-13.kt | 36 +++++++----- .../examples/example-typed-errors-14.kt | 37 ++++++------- .../examples/example-typed-errors-15.kt | 20 ++++--- .../examples/example-typed-errors-16.kt | 28 ++-------- .../examples/example-typed-errors-17.kt | 10 +--- .../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 | 20 +++---- 25 files changed, 251 insertions(+), 274 deletions(-) delete mode 100644 guide/src/test/kotlin/examples/example-typed-errors-21.kt diff --git a/content/docs/learn/design/effects-contexts.md b/content/docs/learn/design/effects-contexts.md index 36531abf..fe7274c5 100644 --- a/content/docs/learn/design/effects-contexts.md +++ b/content/docs/learn/design/effects-contexts.md @@ -7,7 +7,7 @@ sidebar_position: 2 -:::note This article was originally published in the [47 Degrees blog](https://www.47deg.com/blog/effects-contexts/). +:::note This article was originally published in the [47 Degrees blog](https://www.47deg.com/blog/effects-contexts/), and subsequently updated to replace context receivers with context parameters. ::: @@ -188,14 +188,14 @@ Then we cannot define a context requiring `Environment` and `E ### Looking at the future -The future looks quite bright for Kotliners in this respect. For a few versions now, the language includes [_context receivers_](https://github.com/Kotlin/KEEP/issues/259), which would allow a cleaner design for what we are describing using subtyping. Using context receiver, we're able to state: +The future looks quite bright for Kotliners in this respect. For a few versions now, the language includes [_context parameters](https://github.com/Kotlin/KEEP/issues/367), which would allow a cleaner design for what we are describing using subtyping. Using context receiver, we're able to state: ```kotlin -context(Database, Logger) +context(db: Database, logger: Logger) fun User.saveInDb() { ... } ``` -and inject the values by simply nesting the calls to `db` and `stdoutLogger`. Note that sometimes you need a more robust `with` function than the one provided by the standard library, like [this one](https://gist.github.com/carbaj03/4ebd0f8da17c351d4235e1bedd9a36b5), which admits subtyping within contexts. +and inject the values by simply nesting the calls to `db` and `stdoutLogger`, or simply using the `context` function. ## Contexts, effects, algebras diff --git a/content/docs/learn/design/receivers-flatmap.md b/content/docs/learn/design/receivers-flatmap.md index 76c5760b..671e590e 100644 --- a/content/docs/learn/design/receivers-flatmap.md +++ b/content/docs/learn/design/receivers-flatmap.md @@ -7,7 +7,7 @@ sidebar_position: 3 -:::note This article was originally published at [Xebia's blog](https://xebia.com/blog/the-suspend-receivers-style-in-kotlin/). +:::note This article was originally published at [Xebia's blog](https://xebia.com/blog/the-suspend-receivers-style-in-kotlin/). This article mentions context receivers, which are deprecated, and shall be updated once context parameters are released. ::: diff --git a/content/docs/learn/typed-errors/own-error-types.md b/content/docs/learn/typed-errors/own-error-types.md index 6b4f5666..3ff777a6 100644 --- a/content/docs/learn/typed-errors/own-error-types.md +++ b/content/docs/learn/typed-errors/own-error-types.md @@ -74,10 +74,10 @@ fun example() { -If we'd used _context receivers_, defining this DSL would be even more straightforward, and we could use the `Raise` type class directly. +If we'd used _context parameters, defining this DSL would be even more straightforward, and we could use the `Raise` type class directly. -```kotlin -context(Raise>) +``` +context(_: Raise>) fun Lce.bind(): A = when (this) { is Lce.Content -> value is Lce.Failure -> raise(this) 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 286109a6..8f1c1d60 100644 --- a/content/docs/learn/typed-errors/working-with-typed-errors.md +++ b/content/docs/learn/typed-errors/working-with-typed-errors.md @@ -83,13 +83,13 @@ as their first type parameter. The second approach is describing errors as part of the _computation context_ of the function. In that case the ability to finish with logical failures is represented by having `Raise` be part of the context or scope of the function. Kotlin offers two choices here: we can use -an extension receiver or using the more modern context receivers. +an extension receiver, and in the future we may use context parameters. ``` // Raise is extension receiver fun Raise.findUser(id: UserId): User -// Raise is context receiver -context(Raise) fun findUser(id: UserId): User +// Raise is context parameter +context(_: Raise) fun findUser(id: UserId): User ``` Let's define a simple program that _raises_ a _logical failure_ of `UserNotFound` or returns a `User`. We can represent this both as a value `Either`, and as a _computation_ (using `Raise`). @@ -202,21 +202,14 @@ fun example() { Without context receivers, these functions look pretty different depending on if we use `Raise` or `Either`. This is because we sacrifice our _extension receiver_ for `Raise`. -And thus, the `Raise` based computation cannot be an extension function on `User`. With context receivers, we could've defined it as: +And thus, the `Raise` based computation cannot be an extension function on `User`. +In the future, context parameters should allow us to define the function as follows: - -```kotlin -context(Raise) +``` +context(_: Raise) fun User.isValid(): Unit = ensure(id > 0) { UserNotFound("User without a valid id: $id") } ``` - `ensureNotNull` takes a _nullable value_ and a _lazy_ `UserNotFound` value. When the value is null, the _computation_ will result in a _logical failure_ of `UserNotFound`. Otherwise, the value will be _smart-casted_ to non-null, and you can operate on it without checking nullability. @@ -256,7 +249,7 @@ fun example() { ) } ``` - + ## Running and inspecting results @@ -294,7 +287,7 @@ fun example() { ) } ``` - + :::info Fold over all possible cases @@ -328,7 +321,7 @@ fun example() { either { error() } shouldBe UserNotFound.left() } ``` - + + In fact, to define a result with a wrapper type, we recommend to use one of the runners (`either`, `ior`, et cetera), and use `.bind()` to "inject" @@ -385,7 +378,7 @@ val maybeSeven: Either = either { maybeTwo.bind() + maybeFive.bind() } ``` - + ```mermaid graph LR; @@ -429,7 +422,7 @@ fun problematic(n: Int): Either = } } ``` - + ::: @@ -494,7 +487,7 @@ suspend fun example() { }) { e: UserNotFound -> null } shouldBe User(1) } ``` - + Default to `null` is typically not desired since we've effectively swallowed our _logical failure_ and ignored our error. If that was desirable, we could've used nullable types initially. @@ -538,7 +531,7 @@ fun example() { .recover { _: UserNotFound -> raise(OtherError) } shouldBe OtherError.left() } ``` - + The type system now tracks that a new error of `OtherError` might have occurred, but we recovered from any possible errors of `UserNotFound`. This is useful across application layers or in the service layer, where we might want to `recover` from a `DatabaseError` with a `NetworkError` when we want to load data from the network when a database operation failed. @@ -565,7 +558,7 @@ suspend fun Raise.recovery(): User = fetchUser(-1) }) { _: UserNotFound -> raise(OtherError) } ``` - + :::tip DSLs everywhere Since recovery for both `Either` and `Raise` is DSL based, you can also call `bind` or `raise` from both. @@ -624,7 +617,7 @@ suspend fun insertUser(username: String, email: String): Either + This pattern allows us to turn exceptions we want to track into _typed errors_, and things that are **truly** exceptional remain exceptional. @@ -672,7 +665,7 @@ fun example() { (1..10).mapOrAccumulate { isEven2(it).bind() } shouldBe errors } ``` - + We can also provide custom logic to accumulate the errors, typically when we have custom types. @@ -715,7 +708,7 @@ fun example() { (1..10).mapOrAccumulate(MyError::plus) { isEven2(it).bind() } shouldBe error } ``` - + :::tip Accumulating errors but not values @@ -734,7 +727,7 @@ fun example() = either { } } ``` - + ::: @@ -750,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) ``` - + It's customary to define the different problems that may arise from validation as a sealed interface: @@ -790,7 +783,7 @@ fun example() { User("", -1) shouldBe Left(UserProblem.EmptyName) } ``` - + + :::tip Error accumulation and concurrency @@ -870,7 +863,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/gradle/libs.versions.toml b/gradle/libs.versions.toml index 90769db3..e4324c21 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ kotest = "5.9.1" kotlin = "2.1.0" knit = "0.5.0" arrow = "1.2.4" -ksp = "2.0.21-1.0.28" +ksp = "2.1.0-RC2-1.0.28" suspendapp = "0.4.0" kotlinKafka = "0.4.0" cache4k = "0.13.0" diff --git a/guide/build.gradle.kts b/guide/build.gradle.kts index 8515cf20..fcfeed12 100644 --- a/guide/build.gradle.kts +++ b/guide/build.gradle.kts @@ -46,8 +46,4 @@ tasks { setEvents(listOf("passed", "skipped", "failed", "standardOut", "standardError")) } } - - withType().configureEach { - compilerOptions.freeCompilerArgs.add("-Xcontext-receivers") - } } diff --git a/guide/src/test/kotlin/examples/example-typed-errors-04.kt b/guide/src/test/kotlin/examples/example-typed-errors-04.kt index 571ed47b..f5d8fb16 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-04.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-04.kt @@ -1,12 +1,34 @@ // This file was automatically generated from working-with-typed-errors.md by Knit tool. Do not edit. package arrow.website.examples.exampleTypedErrors04 +import arrow.core.Either +import arrow.core.left +import arrow.core.raise.ensureNotNull +import arrow.core.raise.either import arrow.core.raise.Raise -import arrow.core.raise.ensure +import arrow.core.raise.fold +import io.kotest.assertions.fail +import io.kotest.matchers.shouldBe data class User(val id: Long) data class UserNotFound(val message: String) -context(Raise) -fun User.isValid(): Unit = - ensure(id > 0) { UserNotFound("User without a valid id: $id") } +fun process(user: User?): Either = either { + ensureNotNull(user) { UserNotFound("Cannot process null user") } + user.id // smart-casted to non-null +} + +fun Raise.process(user: User?): Long { + ensureNotNull(user) { UserNotFound("Cannot process null user") } + return user.id // smart-casted to non-null +} + +fun example() { + process(null) shouldBe UserNotFound("Cannot process null user").left() + + fold( + { process(User(1)) }, + { _: UserNotFound -> fail("No logical failure occurred!") }, + { i: Long -> i shouldBe 1L } + ) +} diff --git a/guide/src/test/kotlin/examples/example-typed-errors-05.kt b/guide/src/test/kotlin/examples/example-typed-errors-05.kt index eee284a2..10d3dd10 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-05.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-05.kt @@ -2,33 +2,30 @@ package arrow.website.examples.exampleTypedErrors05 import arrow.core.Either +import arrow.core.Either.Left +import arrow.core.Either.Right import arrow.core.left -import arrow.core.raise.ensureNotNull -import arrow.core.raise.either import arrow.core.raise.Raise import arrow.core.raise.fold import io.kotest.assertions.fail import io.kotest.matchers.shouldBe +object UserNotFound data class User(val id: Long) -data class UserNotFound(val message: String) -fun process(user: User?): Either = either { - ensureNotNull(user) { UserNotFound("Cannot process null user") } - user.id // smart-casted to non-null -} +val error: Either = UserNotFound.left() -fun Raise.process(user: User?): Long { - ensureNotNull(user) { UserNotFound("Cannot process null user") } - return user.id // smart-casted to non-null -} +fun Raise.error(): User = raise(UserNotFound) fun example() { - process(null) shouldBe UserNotFound("Cannot process null user").left() + when (error) { + is Left -> error.value shouldBe UserNotFound + is Right -> fail("A logical failure occurred!") + } fold( - { process(User(1)) }, - { _: UserNotFound -> fail("No logical failure occurred!") }, - { i: Long -> i shouldBe 1L } + block = { error() }, + recover = { e: UserNotFound -> e shouldBe UserNotFound }, + transform = { _: User -> fail("A logical failure occurred!") } ) } diff --git a/guide/src/test/kotlin/examples/example-typed-errors-06.kt b/guide/src/test/kotlin/examples/example-typed-errors-06.kt index 543c579d..f28d009a 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-06.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-06.kt @@ -7,6 +7,7 @@ import arrow.core.Either.Right import arrow.core.left import arrow.core.raise.Raise import arrow.core.raise.fold +import arrow.core.raise.either import io.kotest.assertions.fail import io.kotest.matchers.shouldBe @@ -18,14 +19,5 @@ val error: Either = UserNotFound.left() fun Raise.error(): User = raise(UserNotFound) fun example() { - when (error) { - is Left -> error.value shouldBe UserNotFound - is Right -> fail("A logical failure occurred!") - } - - fold( - block = { error() }, - recover = { e: UserNotFound -> e shouldBe UserNotFound }, - transform = { _: User -> fail("A logical failure occurred!") } - ) + either { error() } shouldBe UserNotFound.left() } diff --git a/guide/src/test/kotlin/examples/example-typed-errors-07.kt b/guide/src/test/kotlin/examples/example-typed-errors-07.kt index 5c18ec0d..956320cb 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-07.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-07.kt @@ -4,20 +4,18 @@ package arrow.website.examples.exampleTypedErrors07 import arrow.core.Either import arrow.core.Either.Left import arrow.core.Either.Right -import arrow.core.left +import arrow.core.right import arrow.core.raise.Raise -import arrow.core.raise.fold import arrow.core.raise.either +import arrow.core.raise.fold import io.kotest.assertions.fail import io.kotest.matchers.shouldBe object UserNotFound data class User(val id: Long) -val error: Either = UserNotFound.left() +val user: Either = User(1).right() -fun Raise.error(): User = raise(UserNotFound) +fun Raise.user(): User = User(1) -fun example() { - either { error() } shouldBe UserNotFound.left() -} +fun Raise.res(): User = user.bind() diff --git a/guide/src/test/kotlin/examples/example-typed-errors-08.kt b/guide/src/test/kotlin/examples/example-typed-errors-08.kt index a4b2dea6..b18bccfa 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-08.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-08.kt @@ -11,11 +11,11 @@ import arrow.core.raise.fold import io.kotest.assertions.fail import io.kotest.matchers.shouldBe -object UserNotFound -data class User(val id: Long) +object Problem -val user: Either = User(1).right() +val maybeTwo: Either = either { 2 } +val maybeFive: Either = either { raise(Problem) } -fun Raise.user(): User = User(1) - -fun Raise.res(): User = user.bind() +val maybeSeven: Either = either { + maybeTwo.bind() + maybeFive.bind() +} diff --git a/guide/src/test/kotlin/examples/example-typed-errors-09.kt b/guide/src/test/kotlin/examples/example-typed-errors-09.kt index 6aee6b95..b64703ac 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-09.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-09.kt @@ -2,20 +2,18 @@ package arrow.website.examples.exampleTypedErrors09 import arrow.core.Either -import arrow.core.Either.Left -import arrow.core.Either.Right -import arrow.core.right -import arrow.core.raise.Raise import arrow.core.raise.either -import arrow.core.raise.fold -import io.kotest.assertions.fail -import io.kotest.matchers.shouldBe +import arrow.core.raise.nullable object Problem -val maybeTwo: Either = either { 2 } -val maybeFive: Either = either { raise(Problem) } - -val maybeSeven: Either = either { - maybeTwo.bind() + maybeFive.bind() -} +fun problematic(n: Int): Either = + either { + nullable { + when { + n < 0 -> raise(Problem) + n == 0 -> raise(null) + else -> n + } + } + } diff --git a/guide/src/test/kotlin/examples/example-typed-errors-10.kt b/guide/src/test/kotlin/examples/example-typed-errors-10.kt index 945d523e..6c850aaa 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-10.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-10.kt @@ -2,18 +2,33 @@ package arrow.website.examples.exampleTypedErrors10 import arrow.core.Either +import arrow.core.left +import arrow.core.getOrElse +import arrow.core.raise.ensure import arrow.core.raise.either -import arrow.core.raise.nullable +import arrow.core.raise.Raise +import arrow.core.raise.recover +import io.kotest.assertions.fail +import io.kotest.matchers.shouldBe -object Problem +data class User(val id: Long) +data class UserNotFound(val message: String) -fun problematic(n: Int): Either = - either { - nullable { - when { - n < 0 -> raise(Problem) - n == 0 -> raise(null) - else -> n - } - } - } +suspend fun fetchUser(id: Long): Either = either { + ensure(id > 0) { UserNotFound("Invalid id: $id") } + User(id) +} + +suspend fun Raise.fetchUser(id: Long): User { + ensure(id > 0) { UserNotFound("Invalid id: $id") } + return User(id) +} + +suspend fun example() { + fetchUser(-1) + .getOrElse { e: UserNotFound -> null } shouldBe null + + recover({ + fetchUser(1) + }) { e: UserNotFound -> null } shouldBe User(1) +} diff --git a/guide/src/test/kotlin/examples/example-typed-errors-11.kt b/guide/src/test/kotlin/examples/example-typed-errors-11.kt index d8596c86..5b572080 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-11.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-11.kt @@ -3,32 +3,35 @@ package arrow.website.examples.exampleTypedErrors11 import arrow.core.Either import arrow.core.left -import arrow.core.getOrElse -import arrow.core.raise.ensure -import arrow.core.raise.either import arrow.core.raise.Raise -import arrow.core.raise.recover -import io.kotest.assertions.fail +import arrow.core.raise.either +import arrow.core.raise.ensure +import arrow.core.recover +import arrow.core.right import io.kotest.matchers.shouldBe data class User(val id: Long) data class UserNotFound(val message: String) -suspend fun fetchUser(id: Long): Either = either { +fun fetchUser(id: Long): Either = either { ensure(id > 0) { UserNotFound("Invalid id: $id") } User(id) } -suspend fun Raise.fetchUser(id: Long): User { +fun Raise.fetchUser(id: Long): User { ensure(id > 0) { UserNotFound("Invalid id: $id") } return User(id) } -suspend fun example() { - fetchUser(-1) - .getOrElse { e: UserNotFound -> null } shouldBe null +object OtherError - recover({ +fun example() { + val either: Either = fetchUser(1) - }) { e: UserNotFound -> null } shouldBe User(1) + .recover { _: UserNotFound -> raise(OtherError) } + + either shouldBe User(1).right() + + fetchUser(-1) + .recover { _: UserNotFound -> raise(OtherError) } shouldBe OtherError.left() } diff --git a/guide/src/test/kotlin/examples/example-typed-errors-12.kt b/guide/src/test/kotlin/examples/example-typed-errors-12.kt index f2f37943..a6b3e3d1 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-12.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-12.kt @@ -1,37 +1,21 @@ // This file was automatically generated from working-with-typed-errors.md by Knit tool. Do not edit. package arrow.website.examples.exampleTypedErrors12 -import arrow.core.Either -import arrow.core.left import arrow.core.raise.Raise -import arrow.core.raise.either import arrow.core.raise.ensure -import arrow.core.recover -import arrow.core.right -import io.kotest.matchers.shouldBe +import arrow.core.raise.recover data class User(val id: Long) data class UserNotFound(val message: String) -fun fetchUser(id: Long): Either = either { - ensure(id > 0) { UserNotFound("Invalid id: $id") } - User(id) -} - -fun Raise.fetchUser(id: Long): User { +suspend fun Raise.fetchUser(id: Long): User { ensure(id > 0) { UserNotFound("Invalid id: $id") } return User(id) } object OtherError -fun example() { - val either: Either = - fetchUser(1) - .recover { _: UserNotFound -> raise(OtherError) } - - either shouldBe User(1).right() - - fetchUser(-1) - .recover { _: UserNotFound -> raise(OtherError) } shouldBe OtherError.left() -} +suspend fun Raise.recovery(): User = + recover({ + fetchUser(-1) + }) { _: UserNotFound -> raise(OtherError) } diff --git a/guide/src/test/kotlin/examples/example-typed-errors-13.kt b/guide/src/test/kotlin/examples/example-typed-errors-13.kt index de91da1d..6692f1aa 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-13.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-13.kt @@ -1,21 +1,31 @@ // This file was automatically generated from working-with-typed-errors.md by Knit tool. Do not edit. package arrow.website.examples.exampleTypedErrors13 +import arrow.core.Either +import arrow.core.raise.catch import arrow.core.raise.Raise -import arrow.core.raise.ensure -import arrow.core.raise.recover +import java.sql.SQLException -data class User(val id: Long) -data class UserNotFound(val message: String) - -suspend fun Raise.fetchUser(id: Long): User { - ensure(id > 0) { UserNotFound("Invalid id: $id") } - return User(id) +object UsersQueries { + fun insert(username: String, email: String): Long = 1L } -object OtherError +fun SQLException.isUniqueViolation(): Boolean = true + +data class UserAlreadyExists(val username: String, val email: String) + +suspend fun Raise.insertUser(username: String, email: String): Long = + catch({ + UsersQueries.insert(username, email) + }) { e: SQLException -> + if (e.isUniqueViolation()) raise(UserAlreadyExists(username, email)) + else throw e + } -suspend fun Raise.recovery(): User = - recover({ - fetchUser(-1) - }) { _: UserNotFound -> raise(OtherError) } +suspend fun insertUser(username: String, email: String): Either = + Either.catchOrThrow { + UsersQueries.insert(username, email) + }.mapLeft { e -> + if (e.isUniqueViolation()) UserAlreadyExists(username, email) + else throw e + } diff --git a/guide/src/test/kotlin/examples/example-typed-errors-14.kt b/guide/src/test/kotlin/examples/example-typed-errors-14.kt index b8a8604c..7a689515 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-14.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-14.kt @@ -2,30 +2,25 @@ package arrow.website.examples.exampleTypedErrors14 import arrow.core.Either -import arrow.core.raise.catch +import arrow.core.left +import arrow.core.nonEmptyListOf +import arrow.core.mapOrAccumulate +import arrow.core.raise.either +import arrow.core.raise.ensure import arrow.core.raise.Raise -import java.sql.SQLException +import io.kotest.matchers.shouldBe -object UsersQueries { - fun insert(username: String, email: String): Long = 1L -} +data class NotEven(val i: Int) -fun SQLException.isUniqueViolation(): Boolean = true +fun Raise.isEven(i: Int): Int = + i.also { ensure(i % 2 == 0) { NotEven(i) } } -data class UserAlreadyExists(val username: String, val email: String) +fun isEven2(i: Int): Either = + either { isEven(i) } -suspend fun Raise.insertUser(username: String, email: String): Long = - catch({ - UsersQueries.insert(username, email) - }) { e: SQLException -> - if (e.isUniqueViolation()) raise(UserAlreadyExists(username, email)) - else throw e - } +val errors = nonEmptyListOf(NotEven(1), NotEven(3), NotEven(5), NotEven(7), NotEven(9)).left() -suspend fun insertUser(username: String, email: String): Either = - Either.catchOrThrow { - UsersQueries.insert(username, email) - }.mapLeft { e -> - if (e.isUniqueViolation()) UserAlreadyExists(username, email) - else throw e - } +fun example() { + (1..10).mapOrAccumulate { isEven(it) } shouldBe errors + (1..10).mapOrAccumulate { isEven2(it).bind() } shouldBe errors +} diff --git a/guide/src/test/kotlin/examples/example-typed-errors-15.kt b/guide/src/test/kotlin/examples/example-typed-errors-15.kt index 57fa2b15..79e40ccf 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-15.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-15.kt @@ -3,24 +3,26 @@ package arrow.website.examples.exampleTypedErrors15 import arrow.core.Either import arrow.core.left -import arrow.core.nonEmptyListOf import arrow.core.mapOrAccumulate import arrow.core.raise.either -import arrow.core.raise.ensure +import arrow.core.raise.ensureNotNull import arrow.core.raise.Raise import io.kotest.matchers.shouldBe -data class NotEven(val i: Int) +data class MyError(val message: String) -fun Raise.isEven(i: Int): Int = - i.also { ensure(i % 2 == 0) { NotEven(i) } } +fun Raise.isEven(i: Int): Int = + ensureNotNull(i.takeIf { i % 2 == 0 }) { MyError("$i is not even") } -fun isEven2(i: Int): Either = +fun isEven2(i: Int): Either = either { isEven(i) } -val errors = nonEmptyListOf(NotEven(1), NotEven(3), NotEven(5), NotEven(7), NotEven(9)).left() +operator fun MyError.plus(second: MyError): MyError = + MyError(message + ", ${second.message}") + +val error = MyError("1 is not even, 3 is not even, 5 is not even, 7 is not even, 9 is not even").left() fun example() { - (1..10).mapOrAccumulate { isEven(it) } shouldBe errors - (1..10).mapOrAccumulate { isEven2(it).bind() } shouldBe errors + (1..10).mapOrAccumulate(MyError::plus) { isEven(it) } shouldBe error + (1..10).mapOrAccumulate(MyError::plus) { isEven2(it).bind() } shouldBe error } diff --git a/guide/src/test/kotlin/examples/example-typed-errors-16.kt b/guide/src/test/kotlin/examples/example-typed-errors-16.kt index df6b546f..5cf863eb 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-16.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-16.kt @@ -1,28 +1,12 @@ // This file was automatically generated from working-with-typed-errors.md by Knit tool. Do not edit. package arrow.website.examples.exampleTypedErrors16 -import arrow.core.Either -import arrow.core.left -import arrow.core.mapOrAccumulate import arrow.core.raise.either -import arrow.core.raise.ensureNotNull -import arrow.core.raise.Raise -import io.kotest.matchers.shouldBe +import arrow.core.raise.ensure +import arrow.core.raise.forEachAccumulating -data class MyError(val message: String) - -fun Raise.isEven(i: Int): Int = - ensureNotNull(i.takeIf { i % 2 == 0 }) { MyError("$i is not even") } - -fun isEven2(i: Int): Either = - either { isEven(i) } - -operator fun MyError.plus(second: MyError): MyError = - MyError(message + ", ${second.message}") - -val error = MyError("1 is not even, 3 is not even, 5 is not even, 7 is not even, 9 is not even").left() - -fun example() { - (1..10).mapOrAccumulate(MyError::plus) { isEven(it) } shouldBe error - (1..10).mapOrAccumulate(MyError::plus) { isEven2(it).bind() } shouldBe error +fun example() = either { + forEachAccumulating(1 .. 10) { i -> + ensure(i % 2 == 0) { "$i is not even" } + } } 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 5dd97bde..ce3eb3fd 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-17.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-17.kt @@ -1,12 +1,4 @@ // This file was automatically generated from working-with-typed-errors.md by Knit tool. Do not edit. package arrow.website.examples.exampleTypedErrors17 -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" } - } -} +data class User(val name: String, val age: Int) 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 70edca90..adfd3522 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-18.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-18.kt @@ -1,4 +1,27 @@ // This file was automatically generated from working-with-typed-errors.md by Knit tool. Do not edit. package arrow.website.examples.exampleTypedErrors18 -data class User(val name: String, val age: Int) +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) +} 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 a66bb4db..a9ecad21 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-19.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-19.kt @@ -3,8 +3,11 @@ 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 { @@ -14,14 +17,15 @@ sealed interface 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) + 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() { - User("", -1) shouldBe Left(UserProblem.EmptyName) + User("", -1) shouldBe Left(nonEmptyListOf(UserProblem.EmptyName, UserProblem.NegativeAge(-1))) } 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 fb713e26..953d7f3e 100644 --- a/guide/src/test/kotlin/examples/example-typed-errors-20.kt +++ b/guide/src/test/kotlin/examples/example-typed-errors-20.kt @@ -1,31 +1,18 @@ // 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.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 arrow.core.* +import arrow.core.raise.* import io.kotest.matchers.shouldBe -sealed interface UserProblem { - object EmptyName: UserProblem - data class NegativeAge(val age: Int): UserProblem -} +val stringError: Either = "problem".left() -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) } - } +val intError: Either = either { + // transform error String -> Int + withError({ it.length }) { + stringError.bind() } } - fun example() { - User("", -1) shouldBe Left(nonEmptyListOf(UserProblem.EmptyName, UserProblem.NegativeAge(-1))) + intError shouldBe Either.Left("problem".length) } diff --git a/guide/src/test/kotlin/examples/example-typed-errors-21.kt b/guide/src/test/kotlin/examples/example-typed-errors-21.kt deleted file mode 100644 index 4f4cdcec..00000000 --- a/guide/src/test/kotlin/examples/example-typed-errors-21.kt +++ /dev/null @@ -1,18 +0,0 @@ -// 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 9c0de5b0..3ff2a76b 100644 --- a/guide/src/test/kotlin/examples/test/TypedErrorsTest.kt +++ b/guide/src/test/kotlin/examples/test/TypedErrorsTest.kt @@ -11,6 +11,10 @@ class TypedErrorsTest { arrow.website.examples.exampleTypedErrors03.example() } + @Test fun ExampleTypedErrors04() = runTest { + arrow.website.examples.exampleTypedErrors04.example() + } + @Test fun ExampleTypedErrors05() = runTest { arrow.website.examples.exampleTypedErrors05.example() } @@ -19,16 +23,16 @@ class TypedErrorsTest { arrow.website.examples.exampleTypedErrors06.example() } - @Test fun ExampleTypedErrors07() = runTest { - arrow.website.examples.exampleTypedErrors07.example() + @Test fun ExampleTypedErrors10() = runTest { + arrow.website.examples.exampleTypedErrors10.example() } @Test fun ExampleTypedErrors11() = runTest { arrow.website.examples.exampleTypedErrors11.example() } - @Test fun ExampleTypedErrors12() = runTest { - arrow.website.examples.exampleTypedErrors12.example() + @Test fun ExampleTypedErrors14() = runTest { + arrow.website.examples.exampleTypedErrors14.example() } @Test fun ExampleTypedErrors15() = runTest { @@ -39,8 +43,8 @@ class TypedErrorsTest { arrow.website.examples.exampleTypedErrors16.example() } - @Test fun ExampleTypedErrors17() = runTest { - arrow.website.examples.exampleTypedErrors17.example() + @Test fun ExampleTypedErrors18() = runTest { + arrow.website.examples.exampleTypedErrors18.example() } @Test fun ExampleTypedErrors19() = runTest { @@ -51,8 +55,4 @@ class TypedErrorsTest { arrow.website.examples.exampleTypedErrors20.example() } - @Test fun ExampleTypedErrors21() = runTest { - arrow.website.examples.exampleTypedErrors21.example() - } - }