Dan Miller
12/05/2022, 5:54 PM(A) -> Either<EE, B>
to an Either<E, Option<A>>
?
The expected behavior is:
1. Either.Left<E>
remains unchanged
2. Either.Right<None>
remains unchanged
3. Either.Right<Some<A>>
, with f:
returning an Either.Left<EE>
, returns the Either.Left<EE>
4. Either.Right<Some<A>>
, with f:
returning an Either.Right<B>
, returns the Either.Right<B>
as a Either.Right<Some<B>>
Sample implementation and tests are in the 🧵Dan Miller
12/05/2022, 5:54 PMfun <A, B, E: EE, EE> Either<E, Option<A>>.doThing(fn: (A) -> Either<EE, B>): Either<EE, Option<B>> {
return this.flatMap { o ->
o.fold( { Either.Right(None) }, {
fn(it).map(::Some)
})
}
}
@Test
fun `test doThing`() {
val error = "error"
val success = "success"
val transformed = "transformed"
// Test Either.Left<E>
with<Either<String, Option<Unit>>, Unit>(error.left().doThing { it: Unit -> fail() }) {
assertIs<Either.Left<String>>(this)
assertEquals(error, value)
}
// Test Either.Right<None>
with<Either<String, Option<Unit>>, Unit>(None.right().doThing { fail() }) {
assertIs<Either.Right<None>>(this)
}
// Test Either.Right<Some<A> with doThing returning Either.Left<EE>
with(Some(success).right().doThing { error.left() }) {
assertIs<Either.Left<String>>(this)
assertEquals(error, value)
}
// Test Either.Right<Some<A> with doThing returning Either.Right<B>
with<Either<Unit, Option<String>>, Unit>(Some(success).right().doThing { transformed.right() }) {
assertIs<Either.Right<Some<String>>>(this)
assertEquals(transformed, value.value)
}
}
simon.vergauwen
12/05/2022, 5:57 PMeither {
original.bind().map(f)
}
May I ask why you're using Option
instead of ?
simon.vergauwen
12/05/2022, 6:02 PME: EE, EE
as in your example.Dan Miller
12/05/2022, 6:03 PMOption
over ?
. What are the current recommendations for using Option
from the arrow library.
We have a key value store interface that looks something like:
interface MyService<T> {
// Either.Right<None> signifies no value found
suspend fun get(key: String): Either<ServiceError, Option<T>>
}
Since we were already using Either
to capture errors that are returned from the service, it seemed most natural to use arrow’s Option
as wellDan Miller
12/05/2022, 6:09 PMeither { original.bind().map(f) }
would eval to a Either<E, Option<Either<EE, B>>>
simon.vergauwen
12/05/2022, 6:19 PMeither {
original.bind().map { f(it).bind() }
}
You can safely call bind
again from within map
thanks to Kotlin inline
mechanism.
Often in the case you're sharing, I incorporate an absence error into ServiceError
but this depends a bit on MyService
. If you need to inspect for None
regularly it can also be annoying. However if you only work over T
it can simplify the code a lot.
Additionally, Arrow has some utilities to work over ?
.
either {
val nullable = service.get("id")
ensureNotNull(nullable) { ServiceError }
nullable.function() // smart-casted
}
I just answered a question on StackOverflow when it's recommended to use Option
over nullable.
https://stackoverflow.com/questions/74691989/convert-java-optional-to-kotlin-arrow-option/74692352?noredirect=1#comment131831091_74692352
We're working on improving the documentation to make this more explicit in the Option
documentation.