1

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?

Izbassar Tolegen
  • 1,990
  • 2
  • 20
  • 37

2 Answers2

2

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.

El Paco
  • 546
  • 2
  • 6
1

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.

Izbassar Tolegen
  • 1,990
  • 2
  • 20
  • 37