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.