问题
I have following interfaces:
interface UserRepository {
fun role(codename: String): IO<Option<Role>>
fun accessRights(roleId: Long): IO<List<AccessRight>>
}
Now trying to use it to compose effectfful operations like this:
private fun retrieveRole(roleCodename: String): IO<Option<RoleTo>> =
IO.fx {
val role = userRepository.role(roleCodename).bind()
role.map { r ->
val ar = userRepository.accessRights(r.id).bind()
RoleTo.of(r, ar)
}
}
The code fails to compile on the second bind (call to userRepository.accessRights(r.id).bind()
since bind
is suspend function. How I can properly compose two operations? I don't get why first bind works but second doesn't and I don't want to make my function suspend or I have to do it anyway?
回答1:
This is one frequent gotcha. If you have Option<A>
or Either<E, A>
and you'd like to act on it, your first instinct is to use it on the block:
either.map { !someIO }
The problem is that the left/none option isn't covered. You should act on both sides, and extract out the IO before executing it.
!either.fold({ ioLogError(it) }, { someIo })
For now, as of 0.10, because fold
is an inline function you can use !
inside it too. I cannot promise that'll be the case in the future, as this is an unintended behavior of inline that we left for convenience.
回答2:
I was able to solve issue using traverse
and applicative instance of IO
:
private fun retrieveRole(roleCodename: String): IO<Option<RoleTo>> =
IO.fx {
val role = userRepository.role(roleCodename).bind()
val accessRights = role.traverse(IO.applicative()) {
userRepository.accessRights(it.id)
}.bind()
role.map2(accessRights) {
(r, ar) -> RoleTo.of(r, ar)
}
}
Thanks for pointing out about the fact that map
expects pure functions.
来源:https://stackoverflow.com/questions/58039277/how-to-use-io-compositions-in-arrow-kt-within-sync-context