这是对@luka-jacobowitz提供的方法的轻微改进。在他的方法中,任何发生在“失败”之前的日志都会丢失。根据建议的类型:
type FutureErrorOr[A] = EitherT[Future, Error, A]
type MyStack[A] = WriterT[FutureErrorOr, Vector[String], A]
我们发现,如果我们使用
WriterT
的
run
方法来扩展
MyStack[A]
的值,我们将得到以下类型的值:
FutureErrorOr[(Vector[String], A)]
这与以下内容相同:
EitherT[Future, Error, (Vector[String], A)]
我们可以用EitherT
的value
方法进一步扩展:
Future[Either[Error, (Vector[String], A)]]
在这里,我们可以看到,仅当程序“成功”(即右结合)时,才能检索包含结果日志的元组。如果程序失败,则无法访问程序运行时创建的任何先前日志。如果我们采用原始示例并稍微修改一下,在每个步骤之后记录一些内容,假设第二步返回类型为
Left [Error]
的值:
val program = for {
age <- WriterT.liftF(getAge)
_ <- WriterT.tell(Vector("Got age!"))
dob <- WriterT.liftF(EitherT.fromEither(getDob(age)))
_ <- WriterT.tell(Vector("Got date of birth!"))
} yield {
dob
}
那么当我们评估结果时,只会返回包含错误的左侧情况而没有任何日志。
val expanded = program.run.value
val result = Await.result(expanded, Duration.apply(2, TimeUnit.SECONDS))
为了获取程序运行结果和在程序失败之前生成的日志,我们可以按照以下方式重新排列建议的单子:
type MyWriter[A] = WriterT[Future, Vector[String], A]
type MyStack[A] = EitherT[MyWriter, Error, A]
现在,如果我们使用
EitherT
的
value
方法扩展
MyStack[A]
,我们将得到以下类型的值:
WriterT[Future, Vector[String], Either[Error, A]]
我们可以使用
WriterT
的
run
方法进一步扩展,得到一个包含日志和结果值的元组:
Future[(Vector[String], Either[Error, A])]
采用这种方法,我们可以将程序重写为:
val program = for {
age <- EitherT(WriterT.liftF(getAge.value))
_ <- EitherT.liftF(WriterT.put(())(Vector("Got age!")))
dob <- EitherT.fromEither(getDob(age))
_ <- EitherT.liftF(WriterT.put(())(Vector("Got date of birth!")))
} yield {
dob
}
当我们运行程序时,即使在执行过程中出现故障,我们也可以访问生成的日志:
val expanded = program.value.run
val result = Await.result(expanded, Duration.apply(2, TimeUnit.SECONDS))
不可否认,这种解决方案需要编写更多的样板代码,但我们始终可以定义一些帮助程序来辅助完成:
implicit class EitherTOps[A](eitherT: FutureErrorOr[A]) {
def lift: EitherT[MyWriter, Error, A] = {
EitherT[MyWriter, Error, A](WriterT.liftF[Future, Vector[String], ErrorOr[A]](eitherT.value))
}
}
implicit class EitherOps[A](either: ErrorOr[A]) {
def lift: EitherT[MyWriter, Error, A] = {
EitherT.fromEither[MyWriter](either)
}
}
def log(msg: String): EitherT[MyWriter, Error, Unit] = {
EitherT.liftF[MyWriter, Error, Unit](WriterT.put[Future, Vector[String], Unit](())(Vector(msg)))
}
val program = for {
age <- getAge.lift
_ <- log("Got age!")
dob <- getDob(age).lift
_ <- log("Got date of birth!")
} yield {
dob
}