如何使用组合子嵌入析取的 writer monad 折叠序列?

How to fold a sequence with a writer monad embedding a disjunction using combinators?

我正在进行一些可能会失败的计算,因此我使用了析取类型作为结果。我有一个相当常见的模式,为此我编写了以下函数:

def traverse[A, B, E](a: Iterable[A], b: B)(f: (B, A) => E \/ B): E \/ B =
    a.foldLeft[E\/B](\/-(b)){case (b0, a0) => b0 flatMap (f(_, a0))}

但现在我想记录计算(无论是失败还是成功)。我需要一个具有以下类型的函数:

type Logged[T] = scalaz.Writer[String,T]
def traverseLogged[A, B, E]
       ( a: Iterable[A], lb: Logged[B])
       ( f: (Logged[B], A) => Logged[E \/ B]): Logged[E \/ B] 

但是我找不到 "nice implementation" 依靠组合器。我提出了以下实现:

a.foldLeft[Logged[E \/ B]](lb.map(_.right[E])){
  (lb0, a0) =>
    val (log, value) = lb0.run
    value match {
      case -\/(err) => lb0
      case \/-(b) => f(b.set(log),a0)
    }
}

有没有一种无需 运行 记录的 b 即可实现的方法?

PS:希望问题标题够清楚>_<

首先值得注意的是,您的 traverse 本质上是 foldLeftMfoldLeftM 使用 Foldable 实例进行处理,其中不包括 Iterable,但我建议在使用 Scalaz 时无论如何都要避免使用 Iterable

所以你可以写:

a.foldLeftM[({ type L[x] = E \/ x })#L, B](b)(f)

或者:

type MyErrorOr[A] = MyError \/ A

a.foldLeftM[MyErrorOr, B](b)(f)

Writer 情况下,我建议不要在累加器中建立 Writer 值。相反,您可以使用 EitherT monad 转换器:

type StringWriter[A] = Writer[String, A]
type LoggedOr[E, A] = EitherT[StringWriter, E, A]

现在您可以再次使用 foldLeftM:

def traverseLogged[A, B, E](a: List[A], b: B)(
  f: (B, A) => LoggedOr[E, B]
): LoggedOr[E, B] = a.foldLeftM[({ type L[x] = LoggedOr[E, x] })#L, B](b)(f)(
  EitherT.eitherTMonad[StringWriter, E]
)

(老实说,我不确定为什么你必须在这里显式地提供 monad 实例——如果我们的 LoggedOr 有一个固定的 [=25] 的类型别名,它就可以正常工作=].)

现在,当您 运行 结果时,您将得到 Writer[String, E \/ B],这与原始 traverseLogged 相同。您实际上可以在方法中直接执行 return a Logged,但如果您需要像这样组合多个操作,则保持在 LoggedOr 级别可能会很有用。

这假定您实际上不需要访问 f 中累积的 Writer,但如果您这样做,则表明另一种类型可能更合适,无论如何。