堆叠M,Either和Writer

5 monads scala monad-transformers scala-cats

我目前正在使用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
Run Code Online (Sandbox Code Playgroud)

我现在想介绍一下Writer monad ie

type MyWriter[A] = Writer[Vector[String], ErrorOr[A]]

def getAge: Future[MyWriter[Int]] = ???
def getDob(age: Int): MyWriter[LocalDate] = ???
Run Code Online (Sandbox Code Playgroud)

我的问题是,对调用getAgegetDob调用进行排序的最佳方法是什么?我知道monads可以堆叠,Future -> Writer -> Either但是我可以继续EitherT在这种情况下使用吗?如果是这样的话?

Luk*_*itz 7

是的,你可以继续使用WriterT像这样的monad变换器:

type FutureErrorOr[A] = EitherT[Future, Error, A]
type MyStack[A] = WriterT[FutureErrorOr, Vector[String], A]
Run Code Online (Sandbox Code Playgroud)

如果你解压这种类型,它类似于 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
Run Code Online (Sandbox Code Playgroud)

为了使这更容易,你可以看看cats-mtl.


Lui*_*ina 6

这是@luka-jacobowitz给出的方法的一个细微变化。使用他的方法,在“失败”之前发生的任何日志都将丢失。鉴于建议的类型:

type FutureErrorOr[A] = EitherT[Future, Error, A]
type MyStack[A] = WriterT[FutureErrorOr, Vector[String], A]
Run Code Online (Sandbox Code Playgroud)

我们发现,如果我们MyStack[A]用 的run方法扩展一个值,WriterT我们会得到以下类型的值:

FutureErrorOr[(Vector[String], A)]
Run Code Online (Sandbox Code Playgroud)

这与以下内容相同:

EitherT[Future, Error, (Vector[String], A)]
Run Code Online (Sandbox Code Playgroud)

然后我们可以使用以下value方法进一步扩展EitherT

EitherT[Future, Error, (Vector[String], A)]
Run Code Online (Sandbox Code Playgroud)

在这里我们可以看到,检索包含结果日志的元组的唯一方法是程序是否“成功”(即右结合)。如果程序失败,则无法访问在程序运行时创建的任何以前的日志。

如果我们采用原始示例并稍微修改它以在每一步之后记录一些内容,并且我们假设第二步返回一个 type 值Left[Error]

Future[Either[Error, (Vector[String], A)]]
Run Code Online (Sandbox Code Playgroud)

然后当我们评估结果时,我们将只返回包含错误的左侧案例,而没有任何日志:

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
}
Run Code Online (Sandbox Code Playgroud)

为了获得运行我们的程序所产生的值以及直到程序失败时生成的日志,我们可以像这样对建议的 monad 重新排序:

val expanded = program.run.value // Future(Success(Left(Error)))
val result = Await.result(expanded, Duration.apply(2, TimeUnit.SECONDS)) // Left(Error), no logs!!
Run Code Online (Sandbox Code Playgroud)

现在,如果我们MyStack[A]使用value方法展开,EitherT我们会得到以下类型的值:

type MyWriter[A] = WriterT[Future, Vector[String], A]
type MyStack[A] = EitherT[MyWriter, Error, A]
Run Code Online (Sandbox Code Playgroud)

我们可以使用 的run方法进一步扩展它WriterT,为我们提供一个包含日志和结果值的元组:

WriterT[Future, Vector[String], Either[Error, A]]
Run Code Online (Sandbox Code Playgroud)

使用这种方法,我们可以像这样重写程序:

Future[(Vector[String], Either[Error, A])]
Run Code Online (Sandbox Code Playgroud)

当我们运行它时,即使程序执行过程中出现故障,我们也可以访问结果日志:

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
}
Run Code Online (Sandbox Code Playgroud)

诚然,这个解决方案需要更多样板,但我们总是可以定义一些帮助程序来帮助解决这个问题:

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))
Run Code Online (Sandbox Code Playgroud)