2017-07-03 3 views
4

Ich versuche herauszufinden, wie man dieses Stück Code in einem eleganten pure-funktionalen Stil mit Hilfe von scalaz7 IO und Monade-Transformatoren schreiben kann, aber ich komme einfach nicht dazu.IO und Zukunft [Option] Monade Transformatoren

Man stelle sich vor Ich habe diese einfache API:

def findUuid(request: Request): Option[String] = ??? 
def findProfile(uuid: String): Future[Option[Profile]] = redisClient.get[Profile](uuid) 

diese API verwenden ich unreine Funktion mit OptionT Transformator wie dies leicht schreiben kann:

val profileT = for { 
    uuid <- OptionT(Future.successful(findUuid(request))) 
    profile <- OptionT(findProfile(uuid)) 
} yield profile 
val profile: Future[Option[Profile]] = profileT.run 

Wie Sie bemerkt haben - diese Funktion enthält findProfile() mit einem Nebeneffekt. Ich möchte diesen Effekt innerhalb der IO-Monade isolieren und außerhalb der reinen Funktion interpretieren, aber ich weiß nicht, wie ich alles kombinieren soll.

def findProfileIO(uuid: String): IO[Future[Option[Profile]]] = IO(findProfile(uuid)) 

val profileT = for { 
    uuid <- OptionT(Future.successful(findUuid(request))) 
    profile <- OptionT(findProfileIO(uuid)) //??? how to put Option inside of the IO[Future[Option]] 
} yield profile 
val profile = profileT.run //how to run transformer and interpret IO with the unsafePerformIO()??? 

Irgendwelche Tipps, wie es gemacht werden könnte?

Antwort

3

IO ist mehr für synchrone Effekte gedacht. Task ist mehr was du willst! diese Frage sehen und beantworten: What's the difference between Task and IO in Scalaz?

Sie Ihre Future-Task umwandeln kann und dann eine API wie dieses:

def findUuid(request: Request): Option[String] = ??? 
def findProfile(uuid: String): Task[Option[Profile]] = ??? 

Dies funktioniert, weil Task sowohl synchrone als auch asynchrone Operationen darstellen können, so findUuid kann auch in Task anstelle von IO gewickelt werden.

Dann können Sie diese in OptionT wickeln:

val profileT = for { 
    uuid <- OptionT(Task.now(findUuid(request))) 
    profile <- OptionT(findProfileIO(uuid)) 
} yield profile 

Dann am Ende irgendwo Sie können es laufen:

profileT.run.attemptRun 

prüfen diesen Link für Futures auf Aufgaben und umgekehrt: Scalaz Task <-> Future

+0

Thx @ luka-Jacobowitz Haben Sie Fragen * Ich benutze diesen Code in meiner Spiele-Action.async, also muss ich zurückkehren Zukunft [Ergebnis]. Transforming Task zu scala.Future bedeutet Task Beendigung. Sobald es beendet wird, wird Play's Action synchronisiert. Wissen Sie, wie man eine Aufgabe in eine Zukunft ohne Kündigung umwandeln kann? * Gehen Sie weiter - betrachten Sie IO Monad nur als eine verschobene Berechnung? Wenn es so ist, bedeutet das, dass das ursprüngliche findProfile: Future auch faul ist und die Berechnung in ExecutionContext verschoben wird? In diesem Fall ist es nicht notwendig, Future in IO zu verpacken - diese Funktion ist bereits rein? –

+0

Das Problem mit Future ist, dass es nicht faul ist. Es führt alle Nebenwirkungen aus, die Sie im Futures-Body definieren, wodurch es standardmäßig zu einer unreinen Funktion wird. Sehen Sie hier für mehr darüber: https://www.reddit.com/r/scala/comments/3zofjl/why_is_future_totally_unusable/ Mein Vorschlag wäre, Aufgaben überall zu verwenden und zu oder von Zukunft zu konvertieren, wenn Sie müssen :) –

+0

thank Sie. Habe letzte Nacht im Future/Task-Vergleich geforscht, lies auch diesen Reddit-Beitrag. –

1

Am Ende mit diesem Stück Code, dachte, es könnte für jemanden nützlich sein (Play 2.6).

Die Controller-Methode ist eine reine Funktion, da die Task-Auswertung außerhalb des PureAction ActionBuilder-Controllers stattfindet. Danke an Lukas Antwort!

Immer noch kämpfen mit dem neuen Paradigma der Action-Komposition in Play 2.6, aber das ist eine andere Geschichte.

FrontendController.scala:

def index = PureAction.pure { request => 
    val profileOpt = (for { 
    uuid <- OptionT(Task.now(request.cookies.get("uuid").map(t => uuidKey(t.value)))) 
    profile <- OptionT(redis.get[Profile](uuid).asTask) 
    } yield profile).run 
    profileOpt.map { profileOpt => 
    Logger.info(profileOpt.map(p => s"User logged in - $p").getOrElse("New user, suggesting login")) 
    Ok(views.html.index(profileOpt)) 
    } 
} 

Actions.scala

Convenient Aktion mit dem Task-Auflösung am Ende

class PureAction @Inject()(parser: BodyParsers.Default)(implicit ec: ExecutionContext) extends ActionBuilderImpl(parser) { 
    self => 
    def pure(block: Request[AnyContent] => Task[Result]): Action[AnyContent] = composeAction(new Action[AnyContent] { 
    override def parser: BodyParser[AnyContent] = self.parser 
    override def executionContext: ExecutionContext = self.ec 
    override def apply(request: Request[AnyContent]): Future[Result] = { 
     val taskResult = block(request) 
     taskResult.asFuture //End of the world lives here 
    } 
    }) 
} 

Converters.scala

Task-> Future und Future-> Task implizite Wandler

implicit class FuturePimped[+T](root: => Future[T]) { 
    import scalaz.Scalaz._ 
    def asTask(implicit ec: ExecutionContext): Task[T] = { 
    Task.async { register => 
     root.onComplete { 
     case Success(v) => register(v.right) 
     case Failure(ex) => register(ex.left) 
     } 
    } 
    } 
} 

implicit class TaskPimped[T](root: => Task[T]) { 
    import scalaz._ 
    val p: Promise[T] = Promise() 
    def asFuture: Future[T] = { 
    root.unsafePerformAsync { 
     case -\/(ex) => p.failure(ex);() 
     case \/-(r) => p.success(r);() 
    } 
    p.future 
    } 
} 
Verwandte Themen