scala - 堆叠 M、Either 和 Writer

标签 scala monads 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

我现在想介绍一下 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在这种情况下?如果是这样怎么办?

最佳答案

这与 @luka-jacobowitz 给出的方法略有不同。 .通过他的方法,在“失败”之前发生的任何日志都将丢失。鉴于建议的类型:

type FutureErrorOr[A] = EitherT[Future, Error, A]
type MyStack[A] = WriterT[FutureErrorOr, Vector[String], A]

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

FutureErrorOr[(Vector[String], A)]

这与以下内容相同:
EitherT[Future, Error, (Vector[String], A)]

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

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))) // 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!!

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

type MyWriter[A] = WriterT[Future, Vector[String], A]
type MyStack[A] = EitherT[MyWriter, Error, A]

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

WriterT[Future, Vector[String], Either[Error, A]]

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

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 // 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
}

关于scala - 堆叠 M、Either 和 Writer,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48744146/

相关文章:

scala - 使用 Spark Scala 检查一个数据框列中的值是否存在于另一个数据框列中

scala - SparkML 设置交叉验证器的并行度

scala - 将嵌套 Spark DataFrame 中的列提取为 scala 数组

mongodb - 将数据从HDFS导出到MongoDB

haskell - Haskell IO 的 MonadPlus 定义

haskell - 如何将 IO 操作的结果注入(inject)到非 IO 单子(monad)计算中

haskell - 在 Haskell 中减少单子(monad)

haskell ->>= 和 concatMap 的区别

f# - 重试 monad 和零构造

haskell - 这个简单的函数叫什么?