堆叠单子 Writer 和 OptionT
Stacking monads Writer and OptionT
我有以下代码:
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 transformer OptionT 堆叠在一起。你能建议怎么做吗?
如果你看一下 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
returns 类型 OptionT[Try, Int]
和 WriterT.lift
的结果会将其推送到写入器对象中,该对象也有一个 List[String]
用于您的日志。
最好的方法是将您的服务调用转换为使用 cats-mtl
。
要表示 Try
或 Option
,您可以使用 MonadError
,对于日志记录,您可以使用 FunctorTell
。现在我不知道你在 userService
或 standService
中到底在做什么,但我写了一些代码来演示结果可能是什么样子:
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 转换器。
我有以下代码:
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 transformer OptionT 堆叠在一起。你能建议怎么做吗?
如果你看一下 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
returns 类型 OptionT[Try, Int]
和 WriterT.lift
的结果会将其推送到写入器对象中,该对象也有一个 List[String]
用于您的日志。
最好的方法是将您的服务调用转换为使用 cats-mtl
。
要表示 Try
或 Option
,您可以使用 MonadError
,对于日志记录,您可以使用 FunctorTell
。现在我不知道你在 userService
或 standService
中到底在做什么,但我写了一些代码来演示结果可能是什么样子:
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 转换器。