2014-09-20 11 views
11

Czytam ten świetny artykuł o dependency injection in scala with Reader monad.Jak radzić sobie z monadą `Reader` i` Try`?

Oryginalny przykład działa dobrze, ale wprowadziłem niewielką zmianę typów zwracanych przez UserRepository.get/find. To był User, ale zmieniłem go na Try[User].

Następnie kod nie zostanie skompilowany, próbowałem wiele razy, ale wciąż bez szczęścia.

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

Błąd kompilacji:

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

i

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

Czytam dokument Kleisli.flatMap (typ zwracany z findUser i getUser jest Kleisli), wymaga to typ parametru jest:

B => Kleisli[M, A, C] 

Ponieważ Try nie będzie numerem Kleisli, występują takie błędy.

Nie jestem pewien, jak sobie z tym poradzić. Czy mogę użyć tutaj scala.util.Try? Jak mogę go zmienić na typ KLeisli? Jak mogę sprawić, aby ten przykład działał?

Odpowiedz

20

Można użyć transformatora ReaderT monady, aby skomponować Reader monady i Try monady do pojedynczej monady, że można użyć for -comprehension na itd

ReaderT jest tylko alias typ dla Kleisli, i możesz użyć Kleisli.kleisli zamiast Reader.apply do zbudowania swoich obliczeń. Zauważ, że potrzebujesz scalaz-contrib dla instancji monady dla Try (lub możesz napisać własną - to całkiem proste).

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

Teraz, że zrobił, UserInfo jest znacznie prostsze (i kompiluje teraz, też!):

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

Możemy pokazać to działa:

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

A potem:

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

Dokładnie t w ten sam sposób, w jaki prowadzisz Reader, ale na końcu dostajesz Try.

Powiązane problemy