2014-09-20 3 views
11

나는이 위대한 기사를 dependency injection in scala with Reader monad에 대해 읽었습니다.`Reader` 모나드와`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의 문서 ( findUser의 반환 형식과 getUserKleisli입니다)를 읽어

, 그것은 매개 변수 유형을 필요로 is :

B => Kleisli[M, A, C] 

TryKleisli이 아니기 때문에 이러한 오류가 있습니다.

어떻게 처리해야할지 모르겠다. scala.util.Try을 여기에서 사용할 수 있습니까? KLeisli 유형으로 변환하려면 어떻게해야하나요? 이 예제를 어떻게 만들 수 있습니까?

답변

20

당신은

ReaderTKleisli 단지 타입 별칭 등 Reader 모나드 당신이에 for -comprehension을 사용할 수있는 하나의 모나드에 Try 모나드를 구성하는 ReaderT 모나드 변압기를 사용하고 있습니다 Reader.apply 대신 Kleisli.kleisli을 사용하여 Reader -y 계산을 구성 할 수 있습니다. Try에 대한 모나드 인스턴스에 대해 scalaz-contrib이 필요합니다 (또는 직접 작성할 수도 있습니다. 매우 간단합니다). 이제 끝낼 것을

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은 훨씬 간단 (그것도 지금 컴파일!) : 다음

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")) 
    } 
} 

그리고 :

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}" 
    ) 
} 

우리는 그것이 작동 표시 할 수 있습니다

UserInfo.userInfo("foo").run(repo).foreach(println) 
Map(fullName -> Foo McBar, email -> [email protected], boss -> Bar McFoo) 

정확히 t 당신이 Reader을 달리는 것과 같은 방법으로, 그러나 당신은 Try을 결국 얻습니다.