Hello, I have a question about make `suspend` func...
# arrow
s
Hello, I have a question about make
suspend
functions ‘functional’, possibly using Arrow-Fx. I threaded my question “How can I use
suspend
functions in a functional way” right here:
The
getCity
function of my ViewModel is a suspend function providing data to a
LiveData
instance that is subscribed to by a Fragment. The data-sources (
locationDataSource
and
geoDataSource
) have
suspend
funs as well. The function
getCity
,
getLocation
and
getReverse
are all
suspend
returning an
Either
. The first implementation below works, but only because
flatMap
is an inline function. If it weren’t ‘inline’, this code would not compile, because
getReverse
is
suspend
.
Copy code
private suspend fun getCity(): Either<MainViewModelError, StringResource> {
    return locationDataSource.getLocation()
        .flatMap { (lat, long) -> geoDataSource.getReverse(lat, long) }
        .map { it.asResource }
        .mapLeft { it.asMainViewModelError }
}
I tried to change it using Arrow-Fx
fx { ... effect { ... } .. }
. However, this code fails with a runtime exception:
Copy code
private suspend fun getCity(): Either<MainViewModelError, StringResource> {
    val result: Either<DataError, StringResource> = fx {
        val (lat, long) = !!effect { locationDataSource.getLocation() }
        val nameOfCity = !!effect { geoDataSource.getReverse(lat, long) }

        nameOfCity.asResource
    }

    return result
        .mapLeft { it.asMainViewModelError }
}
When the first
effect
is called, a stack-trace is produced:
Copy code
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property retVal has not been initialized
        at arrow.typeclasses.suspended.BlockingContinuation.getRetVal(MonadSyntax.kt:24)
        at arrow.typeclasses.suspended.MonadSyntax$DefaultImpls.effect(MonadSyntax.kt:12)
        at arrow.typeclasses.MonadContinuation.effect(MonadContinuations.kt:16)
        at io.intrepid.mvvmfp.ui.main.MainViewModel$getCity$result$1$r$1.invokeSuspend(MainViewModel.kt:90)
        at io.intrepid.mvvmfp.ui.main.MainViewModel$getCity$result$1$r$1.invoke(Unknown Source:10)
        at arrow.typeclasses.Monad$fx$wrapReturn$1.invokeSuspend(Monad.kt:80)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
        at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:128)
        at arrow.typeclasses.Monad$DefaultImpls.fx(Monad.kt:81)
        at arrow.core.extensions.EitherMonad$DefaultImpls.fx(Unknown Source:8)
        at arrow.core.extensions.either.monad.EitherMonadKt$monad$1.fx(EitherMonad.kt:194)
        at arrow.typeclasses.MonadContinuation.fx(Unknown Source:7)
        at io.intrepid.mvvmfp.ui.main.MainViewModel$getCity$result$1.invokeSuspend(MainViewModel.kt:89)
        at io.intrepid.mvvmfp.ui.main.MainViewModel$getCity$result$1.invoke(Unknown Source:10)
        at arrow.typeclasses.Monad$fx$wrapReturn$1.invokeSuspend(Monad.kt:80)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
        at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:128)
        at arrow.typeclasses.Monad$DefaultImpls.fx(Monad.kt:81)
        at arrow.core.extensions.EitherMonad$DefaultImpls.fx(Unknown Source:8)
        at arrow.core.extensions.either.monad.EitherMonadKt$monad$1.fx(EitherMonad.kt:194)
        at arrow.typeclasses.Monad$DefaultImpls.binding(Monad.kt:76)
        at arrow.core.extensions.EitherMonad$DefaultImpls.binding(Unknown Source:8)
        at arrow.core.extensions.either.monad.EitherMonadKt$monad$1.binding(EitherMonad.kt:194)
        at arrow.core.extensions.either.monad.EitherMonadKt.binding(EitherMonad.kt:166)
        at io.intrepid.mvvmfp.ui.main.MainViewModel.getCity(MainViewModel.kt:88)
My question is: How to handle
suspend
functions in a functional way (if
flatMap
weren’t inline, i would have been stuck)? Maybe I should not use
fx ...
inside a
suspend
function? But how about the code in the original’s
flatMap
or, If I tried using just
binding { ... }
without
fx { ... }
instead; when calling other
suspend
funs when the lambda is non-suspend? Is there a
binding { ... }
function variant (for
Eithers
) that accepts a suspend-lambda so that other
suspend
functions can be called? Thank you!
I managed to make it ‘suspend safe’ by moving back to version
0.8.2
and by wrapping calls inside
DeferredK
. But this adds a lot of extra code. Is there a simpler/flatter way than this code below?
Copy code
private suspend fun getCity(): Either<MainViewModelError, StringResource> {
        val result = binding {
            val location = DeferredK(coroutineContext) { locationDataSource.getLocation() }.bind()
            val nameOfCity = location.fold({ DeferredK { it.left() } }) { (lat, long) ->
                DeferredK(coroutineContext) { geoDataSource.getReverse(lat, long) }
            }.bind()

            nameOfCity
        }

        return result.await()
            .map { it.asResource }
            .mapLeft { it.asMainViewModelError }
    }
p
We're having a bit of an Arrow Meetup in London tonight. Ping us on Sunday if we haven't replied by then.
s
Enjoy London! I’ll ping you guys if necessary.
@pakoito Ping ! 🤭
p
The runtime exception is a bug somewhere that should be squashed
once that’s done, you should be able to use any suspend function simply by wrapping it on an
effect
block inside an
fx
block
and from there you can “unwrap” it with
bind
,
!
, or destructuring
DeferredK is going away because this new encoding works better and doesn’t have some baked-in behavior from the kx.coroutines lib
s
Thank you. When that bug has been fixed, I should use the implementation from this : https://kotlinlang.slack.com/archives/C5UPMM0A0/p1549634068443700?thread_ts=1549634015.443400&amp;cid=C5UPMM0A0 Is that implementation the right way to go?
p
take into account that snapshot versions are cached and don’t refresh unless you use gradle flag
--refresh-dependencies
and use clean cache and restart in IJ
and we already fixed one exception there
s
Thank you, Paco. I'll retry later today or tomorrow at work.
@pakoito When having, for example, an
IO<Either<Error, T>>
, is their a way to have a
binding
(or
fx
) to be able to deal with
T
instead of
Either<Error,T>
, ie dig down two levels instead of one?
p
Bifunctor IO, BIO<E, A> for short, can model that. I’m writing it on a branch, but cannot give you a date of when it’ll be finished because my schedule for the next month and a half is crazy