2014-09-20 48 views
11

我正在阅读这篇关于dependency injection in scala with Reader monad的文章。如何处理`Reader` monad和`Try`?

原始示例运行良好,但我对UserRepository.get/find的返回类型做了一些更改。这是User,但我将其更改为Try[User]

然后代码不会被编译,我尝试了很多次,但仍然没有幸运。

import scala.util.Try 
import scalaz.Reader 

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) = Reader((userRepository: UserRepository) => 
    userRepository.get(id) 
) 

    def findUser(username: String) = Reader((userRepository: UserRepository) => 
    userRepository.find(username) 
) 
} 

object UserInfo extends Users { 

    def userEmail(id: Int) = { 
    getUser(id) map (ut => ut.map(_.email)) 
    } 

    def userInfo(username: String) = 
    for { 
     userTry <- findUser(username) 
     user <- userTry  // !!!!!!!! compilation error 
     bossTry <- getUser(user.supervisorId) 
     boss <- bossTry  // !!!!!!!! compilation error 
    } yield Map(
     "fullName" -> s"${user.firstName} ${user.lastName}", 
     "email" -> s"${user.email}", 
     "boss" -> s"${boss.firstName} ${boss.lastName}" 
    ) 
} 

的编译错误是:

Error:(34, 12) type mismatch; 
found : scala.util.Try[Nothing] 
required: scalaz.Kleisli[scalaz.Id.Id,?,?] 
     user <- userTry 
     ^

Error:(36, 12) type mismatch; 
found : scala.util.Try[scala.collection.immutable.Map[String,String]] 
required: scalaz.Kleisli[scalaz.Id.Id,?,?] 
     boss <- bossTry 
     ^

我读的Kleisli.flatMap(和getUserKleislifindUser返回类型)的文件,它需要的参数类型是:

B => Kleisli[M, A, C] 

由于Try不会是Kleisli,所以存在这样的错误。

我不知道如何处理它。我可以在这里使用scala.util.Try吗?我怎样才能把它变成KLeisli类型?我怎样才能使这个例子工作?

回答

20

可以使用ReaderT单子变压器组成Reader单子和Try单子到一个单一的单子,你可以使用一个for -comprehension等

ReaderT只是为Kleisli一个类型别名,并您可以使用Kleisli.kleisli而不是Reader.apply来构建您的Reader -y计算。请注意,您需要scalaz-contrib作为Try的monad实例(或者您可以编写自己的代码 - 这非常简单)。

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) 

恰好t他以同样的方式运行Reader,但最后得到Try