堆叠 M、Either 和 Writer
Stacking M, Either and Writer
我目前正在使用 EitherT 堆叠 Futures 和 Eithers:
type ErrorOr[A] = Either[Error, A]
def getAge: Future[ErrorOr[Int]] = ???
def getDob(age: Int): ErrorOr[LocalDate] = ???
for {
age <- EitherT(getAge)
dob <- EitherT.fromEither[Future](getDob(age))
} yield dob
我现在想介绍一下 Writer monad,即
type MyWriter[A] = Writer[Vector[String], ErrorOr[A]]
def getAge: Future[MyWriter[Int]] = ???
def getDob(age: Int): MyWriter[LocalDate] = ???
我的问题是,对 getAge
和 getDob
调用进行排序的最佳方法是什么?我知道 monad 可以堆叠,即 Future -> Writer -> Either
但我可以在这种情况下继续使用 EitherT
吗?如果是这样怎么办?
是的,您可以像这样使用 WriterT
monad 转换器继续使用两者:
type FutureErrorOr[A] = EitherT[Future, Error, A]
type MyStack[A] = WriterT[FutureErrorOr, Vector[String], A]
如果你解压这个类型,它类似于Future[Either[Error, Writer[Vector[String], A]]
现在棘手的部分是将你的函数提升到这个基本 monad 中,所以这里有一些例子:
def getAge: FutureErrorOr[Int] = ???
def getDob(age: Int): ErrorOr[LocalDate] = ???
for {
age <- WriterT.liftF(getAge)
dob <- WriterT.liftF(EitherT.fromEither(getDob(age)))
} yield dob
为了使这更容易,您可以查看 cats-mtl.
这与 @luka-jacobowitz 给出的方法略有不同。使用他的方法,任何在 "failure" 之前发生的日志都将丢失。鉴于建议的类型:
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)]]
这里我们可以看到,如果程序是 "successful"(即右结合),则检索包含结果日志的元组的唯一方法。如果程序失败,则无法访问在程序 运行ning 期间创建的任何先前日志。
如果我们采用原始示例并稍微修改它以在每一步后记录一些内容,我们假设第二步 returns 类型为 Left[Error]
:
的值
val program = for {
age <- WriterT.liftF(getAge)
_ <- WriterT.tell(Vector("Got age!"))
dob <- WriterT.liftF(EitherT.fromEither(getDob(age))) // getDob returns Left[Error]
_ <- WriterT.tell(Vector("Got date of birth!"))
} yield {
dob
}
那么当我们评估结果时,我们将只取回包含错误的左边的案例,没有任何日志:
val expanded = program.run.value // Future(Success(Left(Error)))
val result = Await.result(expanded, Duration.apply(2, TimeUnit.SECONDS)) // Left(Error), no logs!!
为了获得 运行ning 我们的程序产生的值以及程序失败之前生成的日志,我们可以像这样重新排序建议的单子:
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])]
通过这种方法,我们可以re-write这样的程序:
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 // Future(Success((Vector("Got age!), Left(Error))))
val result = Await.result(expanded, Duration.apply(2, TimeUnit.SECONDS)) // (Vector("Got age!), Left(Error))
诚然,这个解决方案需要更多样板文件,但我们总是可以定义一些助手来帮助解决这个问题:
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
}
我目前正在使用 EitherT 堆叠 Futures 和 Eithers:
type ErrorOr[A] = Either[Error, A]
def getAge: Future[ErrorOr[Int]] = ???
def getDob(age: Int): ErrorOr[LocalDate] = ???
for {
age <- EitherT(getAge)
dob <- EitherT.fromEither[Future](getDob(age))
} yield dob
我现在想介绍一下 Writer monad,即
type MyWriter[A] = Writer[Vector[String], ErrorOr[A]]
def getAge: Future[MyWriter[Int]] = ???
def getDob(age: Int): MyWriter[LocalDate] = ???
我的问题是,对 getAge
和 getDob
调用进行排序的最佳方法是什么?我知道 monad 可以堆叠,即 Future -> Writer -> Either
但我可以在这种情况下继续使用 EitherT
吗?如果是这样怎么办?
是的,您可以像这样使用 WriterT
monad 转换器继续使用两者:
type FutureErrorOr[A] = EitherT[Future, Error, A]
type MyStack[A] = WriterT[FutureErrorOr, Vector[String], A]
如果你解压这个类型,它类似于Future[Either[Error, Writer[Vector[String], A]]
现在棘手的部分是将你的函数提升到这个基本 monad 中,所以这里有一些例子:
def getAge: FutureErrorOr[Int] = ???
def getDob(age: Int): ErrorOr[LocalDate] = ???
for {
age <- WriterT.liftF(getAge)
dob <- WriterT.liftF(EitherT.fromEither(getDob(age)))
} yield dob
为了使这更容易,您可以查看 cats-mtl.
这与 @luka-jacobowitz 给出的方法略有不同。使用他的方法,任何在 "failure" 之前发生的日志都将丢失。鉴于建议的类型:
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)]]
这里我们可以看到,如果程序是 "successful"(即右结合),则检索包含结果日志的元组的唯一方法。如果程序失败,则无法访问在程序 运行ning 期间创建的任何先前日志。
如果我们采用原始示例并稍微修改它以在每一步后记录一些内容,我们假设第二步 returns 类型为 Left[Error]
:
val program = for {
age <- WriterT.liftF(getAge)
_ <- WriterT.tell(Vector("Got age!"))
dob <- WriterT.liftF(EitherT.fromEither(getDob(age))) // getDob returns Left[Error]
_ <- WriterT.tell(Vector("Got date of birth!"))
} yield {
dob
}
那么当我们评估结果时,我们将只取回包含错误的左边的案例,没有任何日志:
val expanded = program.run.value // Future(Success(Left(Error)))
val result = Await.result(expanded, Duration.apply(2, TimeUnit.SECONDS)) // Left(Error), no logs!!
为了获得 运行ning 我们的程序产生的值以及程序失败之前生成的日志,我们可以像这样重新排序建议的单子:
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])]
通过这种方法,我们可以re-write这样的程序:
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 // Future(Success((Vector("Got age!), Left(Error))))
val result = Await.result(expanded, Duration.apply(2, TimeUnit.SECONDS)) // (Vector("Got age!), Left(Error))
诚然,这个解决方案需要更多样板文件,但我们总是可以定义一些助手来帮助解决这个问题:
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
}