堆叠monads Writer和OptionT

时间:2018-07-22 16:57:53

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

我有以下代码:

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堆叠在一起。你能建议怎么做吗?

2 个答案:

答案 0 :(得分:4)

最好的方法是将您的服务呼叫转换为使用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])]]

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

答案 1 :(得分:1)

如果查看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]用于记录日志。