2014-09-20 14 views
11

Sto leggendo questo fantastico articolo su dependency injection in scala with Reader monad.Come gestire `Reader` monad e` Try`?

L'esempio originale funziona correttamente, ma ho apportato alcune modifiche ai tipi di ritorno di UserRepository.get/find. Era User, ma l'ho modificato in Try[User].

Quindi il codice non verrà compilato, ci ho provato molte volte, ma ancora senza fortuna.

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

L'errore di compilazione è:

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

e

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

ho letto il documento di Kleisli.flatMap (Il tipo di ritorno di findUser e getUser è Kleisli), richiede il tipo di parametro is:

B => Kleisli[M, A, C] 

Poiché un Try non è un Kleisli, ci sono tali errori.

Non so come gestirlo. Posso usare scala.util.Try qui? Come posso trasformarlo in un tipo KLeisli? Come posso far funzionare questo esempio?

risposta

20

È possibile utilizzare il trasformatore ReaderT Monade per comporre la Reader monade e Try monade in un unico monade, che è possibile utilizzare un for -comprehension su, ecc

ReaderT è solo un tipo di alias per Kleisli, e è possibile utilizzare Kleisli.kleisli anziché Reader.apply per costruire i calcoli Reader -y. Nota che hai bisogno di scalaz-contrib per l'istanza monad per Try (o puoi scrivere il tuo-è piuttosto semplice).

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

Ora che questo è fatto, UserInfo è molto più semplice (e compila ora, anche!):

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

possiamo dimostrare che funziona:

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

E poi:

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

Esattamente t allo stesso modo in cui eseguiresti un Reader, ma alla fine ottieni un Try.