How to handle `Reader` monad and `Try`?

后端 未结 1 807

I\'m reading this great article about dependency injection in scala with Reader monad.

The original example is working well, but I did a little bit change on the re

相关标签:
1条回答
  • 2020-12-24 10:00

    You can use the ReaderT monad transformer to compose the Reader monad and the Try monad into a single monad that you can use a for-comprehension on, etc.

    ReaderT is just a type alias for Kleisli, and you can use Kleisli.kleisli instead of Reader.apply to construct your Reader-y computations. Note that you need scalaz-contrib for the monad instance for Try (or you can write your own—it's pretty straightforward).

    import scala.util.Try
    import scalaz._, Scalaz._
    import scalaz.contrib.std.utilTry._
    
    case class User(
      email: String,
      supervisorId: Int,
      firstName: String,
      lastName: String
    )
    
    trait UserRepository {
      def get(id: Int): Try[User]
    
      def find(username: String): Try[User]
    }
    
    trait Users {
      def getUser(id: Int): ReaderT[Try, UserRepository, User] =
        Kleisli.kleisli(_.get(id))
    
      def findUser(username: String): ReaderT[Try, UserRepository, User] =
        Kleisli.kleisli(_.find(username))
    }
    

    Now that that's done, UserInfo is much simpler (and it compiles now, too!):

    object UserInfo extends Users {
      def userEmail(id: Int) = getUser(id).map(_.email)
    
      def userInfo(
        username: String
      ): ReaderT[Try, UserRepository, Map[String, String]] =
        for {
          user <- findUser(username)
          boss <- getUser(user.supervisorId)
        } yield Map(
          "fullName" -> s"${user.firstName} ${user.lastName}",
          "email" -> s"${user.email}",
          "boss" -> s"${boss.firstName} ${boss.lastName}"
        )
    }
    

    We can show it works:

    import scala.util.{ Failure, Success }
    
    val repo = new UserRepository {
      val bar = User("bar@mcfoo.com", 0, "Bar", "McFoo")
      val foo = User("foo@mcbar.com", 0, "Foo", "McBar")
    
      def get(id: Int) = id match {
        case 0 => Success(bar)
        case 1 => Success(foo)
        case i => Failure(new Exception(s"No user with id $i"))
      }
    
      def find(username: String) = username match {
        case "bar" => Success(bar)
        case "foo" => Success(foo)
        case other => Failure(new Exception(s"No user with name $other"))
      }
    }
    

    And then:

    UserInfo.userInfo("foo").run(repo).foreach(println)
    Map(fullName -> Foo McBar, email -> foo@mcbar.com, boss -> Bar McFoo)
    

    Exactly the same way you'd run a Reader, but you get a Try at the end.

    0 讨论(0)
提交回复
热议问题