首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >IO和Future[Option]单端变压器

IO和Future[Option]单端变压器
EN

Stack Overflow用户
提问于 2017-07-03 14:19:50
回答 2查看 935关注 0票数 4

我试图弄清楚如何使用scalaz7、IO和monad转换器以一种优雅的纯功能风格编写这段代码,但我就是无法理解它。

想象一下我有一个简单的API:

代码语言:javascript
复制
def findUuid(request: Request): Option[String] = ???
def findProfile(uuid: String): Future[Option[Profile]] = redisClient.get[Profile](uuid)

使用这个API,我可以很容易地用OptionT转换器编写这样的不纯函数:

代码语言:javascript
复制
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内部隔离这一效果,并在纯函数之外进行解释,但不知道如何将其组合在一起。

代码语言:javascript
复制
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()??? 

对于如何做这件事,有什么建议吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-07-03 15:03:33

IO更多地用于同步效果。Task是你想要的!请参见以下问题和答案:What's the difference between Task and IO in Scalaz?

您可以将Future转换为Task,然后拥有如下API:

代码语言:javascript
复制
def findUuid(request: Request): Option[String] = ??? 
def findProfile(uuid: String): Task[Option[Profile]] = ???

这是因为Task既可以表示同步操作,也可以表示异步操作,所以findUuid也可以用Task而不是IO包装。

然后您可以将这些包装在OptionT中。

代码语言:javascript
复制
val profileT = for {
  uuid <- OptionT(Task.now(findUuid(request)))
  profile <- OptionT(findProfileIO(uuid))
} yield profile

在最后你可以运行它的地方:

代码语言:javascript
复制
profileT.run.attemptRun

查看此链接以将期货转换为任务,反之亦然:Scalaz Task <-> Future

票数 3
EN

Stack Overflow用户

发布于 2017-07-06 15:33:58

最后得到了这段代码,认为它可能对某人有用(Play 2.6)。

控制器的方法是一个纯函数,因为任务评估发生在PureAction ActionBuilder内部的控制器之外。感谢卢卡的回答!

在游戏2.6中,仍在与新的动作构图范式作斗争,但这是另一个故事。

FrontendController.scala:

代码语言:javascript
复制
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

操作方便,任务解析在末尾

代码语言:javascript
复制
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

任务->未来和未来->任务隐式转换器

代码语言:javascript
复制
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
  }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/44887942

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档