Search code examples
kotlinarrow-kt

How to abstract from Try in arrow-kt


I'm using Arrow in my Kotlin backend project. I have repositories like this:

interface UserRepository {
  fun user(username: String): Try<Option<User>>
}

Now I want to go step further and abstract from concrete Try type with returning Kind<F, Option<User>> instead. I was able to do it with this code:

interface UserRepository<F> {
  fun user(username: String): Kind<F, Option<User>>
}

class IdRepository : UserRepository<ForId> {
  fun user(username: String): Kind<ForId<Option<User>>> =
    if (username == "known") Id.just(Some(User()))
    else Id.just(None)
}

But now I'm struggling to use it. I don't understand how we can say that the F in userRepository has to be a Monad, so that it can be used in monad comprehension block. Suppose I have some class defined like this:

class UserService<F>(ME: MonadError<F, Throwable>, repo: UserRepository<F>) 
  : MonadError<F, Throwable> by ME {
  fun someOperations(username: String) : Kind<F, User> = bindingCatch {
    val (user) = repo.user(username)
    user.fold({ /* create user */ }, { /* return user */ })
  }
}

The compiler complains that it can't bind user on line repo.user as it requires the Kind<ForTry, ...> but the repo.user returns Kind<F, ...> which is unknown here. How to properly achieve abstraction from Try so that I can implement repositories with Id instances and how to use such repositories in service classes?


Solution

  • In 0.10.0 you can use the Fx type class to perform monad bind. It's variants are available as described in the kdoc over your example where each one of them represents the level of power you want. In practice, most apps use IO.fx since effects can only be purely encapsulated in IO. You may only replace runtimes as long as they support suspension if you are working with side effects so this basically narrows down your runtime options to instances of Async<F> since suspension implies potential async work. That is IO, Rx, etc... but never Try, Either, ... those are good for eager non-effectful pure computations

    /**
     * Fx allows you to run pure sequential code as if it was imperative.
     *
     * @see [arrow.typeclasses.suspended.monad.Fx] // Anything with flatMap
     * @see [arrow.typeclasses.suspended.monaderror.Fx] //Try, Either etc stop here
     * @see [arrow.fx.typeclasses.suspended.monaddefer.Fx] // IO
     * @see [arrow.fx.typeclasses.suspended.concurrent.Fx] // IO
     */
    class UserService<F>(ME: MonadError<F, Throwable>, repo: UserRepository<F>) 
      : MonadError<F, Throwable> by ME {
    
      fun someOperations(username: String) : Kind<F, User> = 
        fx.monadThrow {
          val user = !repo.user(username)
          user.fold({ /* create user */ }, { /* return user */ })
        }
      }
    
    }
    

    If you'd like a more detailed explanation swing by the https://slack.kotlinlang.org #arrow channel and we'll be happy to help and hang out and discuss FP in Kotlin

    Cheers!