Is there any neat means to apply a mapping functio...
# arrow
d
Is there any neat means to apply a mapping function f:
(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 🧵
Copy code
fun <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)
        }
    }
s
Hey @Dan Miller, Simplest way is:
Copy code
either {
  original.bind().map(f)
}
May I ask why you're using
Option
instead of
?
This only works for
E: EE, EE
as in your example.
d
There is no technical reason for choosing
Option
over
?
. What are the current recommendations for using
Option
from the arrow library. We have a key value store interface that looks something like:
Copy code
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 well
Also I do not see how you example would work, by my understanding,
either { original.bind().map(f) }
would eval to a
Either<E, Option<Either<EE, B>>>
s
Oh, my apologies.
Copy code
either {
  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
?
.
Copy code
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.