【问题标题】:Stacking monads Writer and OptionT堆叠 monads Writer 和 OptionT
【发布时间】:2018-07-22 16:57:52
【问题描述】:

我有以下代码:

override def getStandsByUser(email: String): Try[Seq[Stand]] =
  (for {
    user <- OptionT(userService.findOneByEmail(email)): Try[Option[User]]
    stands <- OptionT.liftF(standService.list()):[Try[List[Stand]]]
    filtered = stands.filter(stand => user.stands.contains(stand.id))
  } yield filtered).getOrElse(Seq())
}

我想在处理的每个阶段添加日志记录 - 所以我需要引入 writer monad 并将其与 monad 转换器 OptionT 堆叠在一起。您能否建议如何做到这一点?

【问题讨论】:

    标签: scala monad-transformers scala-cats writer-monad


    【解决方案1】:

    最好的方法是将您的服务调用转换为使用cats-mtl

    对于表示TryOption,您可以使用MonadError,对于日志记录,您可以使用FunctorTell。现在我不知道你在userServicestandService 中到底在做什么,但我写了一些代码来演示结果可能是什么样子:

    type Log = List[String]
    
    //inside UserService
    def findOneByEmail[F[_]](email: String)
      (implicit F: MonadError[F, Error], W: FunctorTell[F, Log]): F[User] = ???
    
    //inside StandService
    def list[F[_]]()
      (implicit F: MonadError[F, Error], W: FunctorTell[F, Log]): F[List[Stand]] = ???
    
    def getStandsByUser[F[_]](email: String)
    (implicit F: MonadError[F, Error], W: FunctorTell[F, Log]): F[List[Stand]] =
      for {
        user <- userService.findOneByEmail(email)
        stands <- standService.list()
      } yield stands.filter(stand => user.stands.contains(stand.id))
    
    
    //here we actually run the function
    val result =
      getStandsByUser[WriterT[OptionT[Try, ?], Log, ?] // yields WriterT[OptionT[Try, ?], Log, List[Stand]]
        .run  // yields OptionT[Try, (Log, List[Stand])]
        .value // yields Try[Option[(Log, List[Stand])]]
    

    这样我们可以避免对liftF 的所有调用,并轻松组合我们的不同服务,即使它们在运行时会使用不同的 monad 转换器。

    【讨论】:

      【解决方案2】:

      如果您查看cats.data.Writer 的定义,您会发现它是cats.data.WriterT 的别名,效果固定为Id

      您想要做的是直接使用WriterT 而不是Id 使用OptionT[Try, YourType]

      下面是一个小代码示例,说明如何实现这一点:

      object Example {
      
        import cats.data._
        import cats.implicits._
      
        type MyType[A] = OptionT[Try, A]
      
        def myFunction: MyType[Int] = OptionT(Try(Option(1)))
      
        def main(args: Array[String]): Unit = {
          val tmp: WriterT[MyType, List[String], Int] = for {
            _ <- WriterT.tell[MyType, List[String]](List("Before first invocation"))
            i <- WriterT.liftF[MyType, List[String], Int](myFunction)
            _ <- WriterT.tell[MyType, List[String]](List("After second invocation"))
            j <- WriterT.liftF[MyType, List[String], Int](myFunction)
            _ <- WriterT.tell[MyType, List[String]](List(s"Result is ${i + j}"))
          } yield i + j
      
          val result: Try[Option[(List[String], Int)]] = tmp.run.value
          println(result)
          // Success(Some((List(Before first invocation, After second invocation, Result is 2),2)))
        }
      
      }
      

      类型注释使这有点难看,但是根据您的用例,您也许可以摆脱它们。如您所见,myFunction 返回OptionT[Try, Int] 类型的结果,WriterT.lift 会将其推送到一个写入器对象中,该对象也有一个List[String] 用于您的日志。

      【讨论】:

        猜你喜欢
        • 2018-07-22
        • 1970-01-01
        • 1970-01-01
        • 2019-10-30
        • 2023-03-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多