Укладка монад Writer и 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())
}
Я хочу добавить протоколирование на каждом этапе обработки - поэтому мне нужно представить монаду писателя и сложить ее с монадным преобразователем OptionT. Не могли бы вы предложить, как это сделать?
2 ответа
Лучший способ сделать это - преобразовать ваши сервисные вызовы в использование 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
и легко составлять наши различные сервисы, даже если они будут использовать разные монадные преобразователи во время выполнения.
Если вы посмотрите на определение 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]
для ваших журналов.