【问题标题】:IO and Future[Option] monad transformersIO 和 Future[Option] monad 转换器
【发布时间】:2017-12-06 20:41:52
【问题描述】:

我正在尝试弄清楚如何使用 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()??? 

关于如何完成的任何建议?

【问题讨论】:

    标签: scala scalaz monad-transformers io-monad


    【解决方案1】:

    IO 更多地用于同步效果。 Task 更多是你想要的! 看到这个问答:What's the difference between Task and IO in Scalaz?

    您可以将您的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
    

    查看此链接以将 Futures 转换为 Tasks,反之亦然:Scalaz Task <-> Future

    【讨论】:

    • Thx @luka-jacobowitz 有问题 * 我在 Play 的 Action.async 中使用此代码,所以我必须返回 Future[Result]。将 Task 转换为 scala.Future 意味着 Task 终止。一旦它终止,Play 的 Action 就会同步。您知道如何在不终止的情况下将 Task 转换为 Future 吗? * 更进一步——你认为 IO Monad 只是一种延迟计算吗?如果是这样,是否意味着原来的 findProfile:Future 也是惰性的,并且它的计算也在 ExecutionContext 中被推迟了?在这种情况下,可能没有必要将 Future 包装到 IO 中——这个函数已经是纯函数了?
    • Future 的问题在于它并不懒惰。它将执行您在 Futures 主体中定义的任何副作用,默认情况下使其成为不纯函数。有关更多信息,请参见此处:reddit.com/r/scala/comments/3zofjl/… 我的建议是在任何地方都使用 Tasks,并在需要时转换为 Future 或从 Future 转换:)
    • 谢谢。昨晚花了很多时间研究 Future/Task 比较,也请阅读这篇 reddit 帖子。
    【解决方案2】:

    结束了这段代码,认为它可能对某人有用(Play 2.6)。

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

    尽管仍在 Play 2.6 中与新的动作组合范例作斗争,但这是另一回事。

    前端控制器.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
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-27
      • 2015-08-15
      • 1970-01-01
      • 2020-03-16
      • 2021-04-20
      • 2015-11-03
      相关资源
      最近更新 更多