Created
January 1, 2023 14:02
-
-
Save raulraja/0e54a2378df919a965e39d33dcb32e40 to your computer and use it in GitHub Desktop.
Error Handling Evolution with Arrow 2.0.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package demo | |
import arrow.core.Either | |
import arrow.core.None | |
import arrow.core.Option | |
import arrow.core.Some | |
import arrow.core.continuations.Raise | |
import arrow.core.continuations.effect | |
import arrow.core.continuations.either | |
import arrow.core.continuations.toEither | |
/** | |
* Model | |
*/ | |
data class Person(val name: String) | |
data class Address(val country: Country) | |
@JvmInline | |
value class Code(val value: String) | |
data class Country(val code: Code) | |
/** | |
* Use case: | |
* | |
* Access the country code of a person's address through a remote service | |
*/ | |
suspend fun Person.address(): Either<AddressStatus, Address> = | |
Either.Left(AddressStatus.ServiceIsDown) | |
/** | |
* When accessing the address we may get back instead an AddressStatus | |
*/ | |
sealed class AddressStatus { | |
object Relocating : AddressStatus() | |
object Missing : AddressStatus() | |
object ServiceIsDown: AddressStatus() | |
} | |
/** | |
* [RuntimeException]. | |
* Good: `Code` is a simple return type | |
* Bad: Caller does not know this throws | |
* Ugly: Can crash your application | |
*/ | |
suspend fun Person.countryCodeExceptions(): Code = | |
when (val address = address()) { | |
is Either.Left -> throw RuntimeException(address.value.toString()) // costly to create stack trace | |
is Either.Right -> address.value.country.code | |
} | |
/** | |
* [Option] | |
* Good: Caller needs to handle | |
* Bad: We loose info of why address is not there, | |
* Caller needs to deal with boxed types | |
* Ugly: New allocations for composition | |
*/ | |
suspend fun Person.countryCodeOption(): Option<Code> = | |
when (val address = address()) { | |
is Either.Left -> None | |
is Either.Right -> Some(address.value.country.code) | |
} | |
/** | |
* [Either] | |
* Good: Caller needs to handle, we recover info why address may not be there | |
* Bad: Caller needs to deal with boxed types | |
* Ugly: New allocations for composition | |
*/ | |
suspend fun Person.countryCodeEither(): Either<AddressStatus, Code> = | |
when (val address = address()) { | |
is Either.Left -> address | |
is Either.Right -> Either.Right(address.value.country.code) | |
} | |
/** | |
* [either] | |
* Good: Caller needs to handle, we recover info why address may not be there, | |
* better monadic syntax | |
* Bad: Caller needs to deal with boxed types | |
* Ugly: Explicit DSL either block | |
*/ | |
suspend fun Person.countryCodeEitherDSL(): Either<AddressStatus, Code> = | |
either { address().bind().country.code } | |
/** | |
* [Raise] | |
* Good: Caller needs to handle, direct syntax | |
* Bad?: We are back to a form of typed exceptions | |
* Ugly?: Need to learn context receivers. | |
* | |
*/ | |
context(Raise<AddressStatus>) | |
suspend fun Person.countryCode(): Code = | |
address().bind().country.code | |
/** | |
* If we redefined our `address` function to use `Raise` | |
*/ | |
context(Raise<AddressStatus>) | |
suspend fun Person.addressRaised(): Address = | |
raise(AddressStatus.ServiceIsDown) // Stack trace disabled, may be enabled with config | |
/** | |
* Then we don't need to `bind` | |
*/ | |
context(Raise<AddressStatus>) /* Dependencies */ | |
suspend fun Person /* Receiver */.countryCodeRaised(): Code /* <-- Simple return */ = | |
addressRaised().country.code /* <-- Direct syntax, no bind */ | |
suspend fun main() { | |
val j = Person("j") | |
//val program: Code = j.countryCodeExceptions() // java.lang.RuntimeException: demo.AddressStatus$ServiceIsDown | |
val programOption: Option<Code> = j.countryCodeOption() // we don't know why it failed | |
val programEither: Either<AddressStatus, Code> = j.countryCodeEither() // we have to deal with boxes | |
val programEffect = effect { j.countryCodeRaised() } // at the edge we wrap in effect | |
println(programOption) // None | |
println(programEither) // Left | |
// effects are controlled as a function until the edge. | |
// At that point they can be folded to a terminal value | |
println(programEffect.toEither()) // Left | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment