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

旧时模样 提交于 2019-11-29 23:19:15

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!