您可以使用ReaderT
Monad 变压器来组成Reader
单子和Try
monad 成一个单一的 monad,你可以使用for
- 理解等
ReaderT
只是一个类型别名Kleisli
,你可以使用Kleisli.kleisli
代替Reader.apply
构建你的Reader
-y 计算。请注意,您需要scalaz-contrib对于 monad 实例Try
(或者您可以自己编写 - 这非常简单)。
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))
}
既然已经完成了,UserInfo
更简单(而且现在也可以编译!):
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}"
)
}
我们可以展示它的工作原理:
import scala.util.{ Failure, Success }
val repo = new UserRepository {
val bar = User("[email protected]", 0, "Bar", "McFoo")
val foo = User("[email protected]", 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"))
}
}
进而:
UserInfo.userInfo("foo").run(repo).foreach(println)
Map(fullName -> Foo McBar, email -> [email protected], boss -> Bar McFoo)
与您运行的方式完全相同Reader
,但是你会得到一个Try
在最后。