IO 和 Future[Option] monad 转换器
IO and Future[Option] monad transformers
我正在尝试弄清楚如何使用 scalaz7 IO 和 monad 转换器以优雅的纯函数式风格编写这段代码,但就是无法理解它。
想象一下我有这个简单的 API:
def findUuid(request: Request): Option[String] = ???
def findProfile(uuid: String): Future[Option[Profile]] = redisClient.get[Profile](uuid)
使用这个 API 我可以像这样使用 OptionT 转换器轻松编写不纯函数:
val profileT = for {
uuid <- OptionT(Future.successful(findUuid(request)))
profile <- OptionT(findProfile(uuid))
} yield profile
val profile: Future[Option[Profile]] = profileT.run
如您所见 - 此函数包含具有副作用的 findProfile()。我想在 IO monad 内部隔离这种效果并在纯函数之外进行解释,但不知道如何将它们组合在一起。
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()???
关于如何完成的任何建议?
IO
更适用于同步效果。 Task
更多你想要的!
看到这个问答:
您可以将 Future
转换为 Task
,然后像这样获得 API:
def findUuid(request: Request): Option[String] = ???
def findProfile(uuid: String): Task[Option[Profile]] = ???
这是可行的,因为 Task
可以表示同步和异步操作,所以 findUuid
也可以包装在 Task
中而不是 IO
。
然后你可以将它们包装在 OptionT
:
val profileT = for {
uuid <- OptionT(Task.now(findUuid(request)))
profile <- OptionT(findProfileIO(uuid))
} yield profile
然后在最后的某个地方你可以运行它:
profileT.run.attemptRun
查看此 link 以将 Futures 转换为 Tasks,反之亦然:Scalaz Task <-> Future
结束这段代码,认为它可能对某些人有用(玩 2.6)。
控制器的方法是一个纯函数,因为任务评估发生在 PureAction ActionBuilder 内部的控制器之外。感谢 Luka 的回答!
虽然仍在为 Play 2.6 中的新动作组合范式而苦苦挣扎,但这是另一回事了。
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
最后有任务解析的便捷操作
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 和 Future->Task 隐式转换器
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
}
}
我正在尝试弄清楚如何使用 scalaz7 IO 和 monad 转换器以优雅的纯函数式风格编写这段代码,但就是无法理解它。
想象一下我有这个简单的 API:
def findUuid(request: Request): Option[String] = ???
def findProfile(uuid: String): Future[Option[Profile]] = redisClient.get[Profile](uuid)
使用这个 API 我可以像这样使用 OptionT 转换器轻松编写不纯函数:
val profileT = for {
uuid <- OptionT(Future.successful(findUuid(request)))
profile <- OptionT(findProfile(uuid))
} yield profile
val profile: Future[Option[Profile]] = profileT.run
如您所见 - 此函数包含具有副作用的 findProfile()。我想在 IO monad 内部隔离这种效果并在纯函数之外进行解释,但不知道如何将它们组合在一起。
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()???
关于如何完成的任何建议?
IO
更适用于同步效果。 Task
更多你想要的!
看到这个问答:
您可以将 Future
转换为 Task
,然后像这样获得 API:
def findUuid(request: Request): Option[String] = ???
def findProfile(uuid: String): Task[Option[Profile]] = ???
这是可行的,因为 Task
可以表示同步和异步操作,所以 findUuid
也可以包装在 Task
中而不是 IO
。
然后你可以将它们包装在 OptionT
:
val profileT = for {
uuid <- OptionT(Task.now(findUuid(request)))
profile <- OptionT(findProfileIO(uuid))
} yield profile
然后在最后的某个地方你可以运行它:
profileT.run.attemptRun
查看此 link 以将 Futures 转换为 Tasks,反之亦然:Scalaz Task <-> Future
结束这段代码,认为它可能对某些人有用(玩 2.6)。
控制器的方法是一个纯函数,因为任务评估发生在 PureAction ActionBuilder 内部的控制器之外。感谢 Luka 的回答!
虽然仍在为 Play 2.6 中的新动作组合范式而苦苦挣扎,但这是另一回事了。
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
最后有任务解析的便捷操作
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 和 Future->Task 隐式转换器
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
}
}