tavish pegram
08/16/2022, 8:35 PMsealed interface FooFailure {
object Failure1: FooFailure
object Failure2: FooFailure
}
fun foo(): Either<FooFailure, Unit> = TODO()
sealed interface BarFailure {
data class FooFailed(message: String): BarFailure
object Failure1: BarFailure
...
}
fun bar(input: Input): Either<BarFailure, Unit> = either {
input.validate().bind() // Could be BarFailure.Failure1
foo().mapLeft { when (it) {
FooFailure.Failure1 -> BarFailure.FooFailed("Failure 1")
FooFailure.Failure2 -> BarFailure.FooFailed("Failure 2")
}}.bind()
}
maybe
sealed interface FooFailure {
object Failure1: FooFailure
object Failure2: FooFailure
}
fun foo(): Either<FooFailure, Unit> = TODO()
sealed interface BarFailure {
object Failure1: BarFailure
...
}
fun bar(input: Input): Either<BarFailure|FooFailure, Unit> = either {
input.validate().bind() // Could be BarFailure.Failure1 or some other bar specific failure
foo().bind()
}
but still being able to exhaustively when/pattern match on it. Ideally, in a flat way, but even having it be nested is fine.
bar(someInput()).fold(
ifLeft = { when (it) {
FooFailure.Failure1 -> TODO()
FooFailure.Failure2 -> TODO()
BarFailure.Failure1 -> TODO()
}},
ifRight = { TODO() }
)
Thanks!simon.vergauwen
08/17/2022, 6:42 AMbut have been using arrow a lot at work which has been great.❤️ Any feedback you might have is very welcome Tavish! 🙏
tavish pegram
08/17/2022, 5:50 PMeither
block a lot, because its great. But it does feel like it adds a bit of “clutter” and another bit of indentation to business logic. This is super nitpicky, but it might be cool to be able to use it as an annotation in some cases (similar to how I like to make logging / observability be an annotation so a function isn’t cluttered with log / dd metric calls that distract from the actual business logic). I assume it wouldn’t be as flexible as the function version though, but we could probably still get a lot of use out of it.
@Observe("metric and logging name goes here") // Log input / output / time of execution and generate some basic data dog metrics
@Either // Hypothetically, this is the same as just adding `either {}` around the contents of this function. I assume the ordering of these annotation would matter. Do we lose typesafety when we start using annotations like this?
suspend fun foo(input: Input): Either<FooFailure, SomeResult> {
// Only Bizness in here
val x = bar(input.a).bind()
return baz(x).bind()
}
The above fake code also assumes that bar and baz return FooFailure so we don’t have to do any extra mapping, which is the ask in the main post.simon.vergauwen
08/17/2022, 6:01 PMcontext(EffectScope<String>)
and you can now monadically raise String
as an error inside of it.
I.e. either<String, Int> { /* this is of type EffectScope<String> */ }
Further more, you can add multiple of them on top of you function. This solves the layer problem
context(EffectScope<String>)
suspend fun example(): Int = 1
suspend fun example2(): Either<String, Int> = either { example() }
object Error
context(EffectScope<String>, EffectScope<Error>)
suspend fun example3(): Int {
example()
shift(Error)
}
context(EffectScope<Error>)
suspend fun example4(): Int =
either { example() }.getOrHandle { -1 }