如何使用组合器嵌入分离的编写器monad折叠序列?

时间:2015-05-28 15:48:21

标签: scala scalaz

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

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] 

然而,我找不到依靠组合器的“好实现”。我想出了以下实现:

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:我希望问题标题足够清晰> _<

1 个答案:

答案 0 :(得分:2)

首先,值得注意的是,traverse基本上是foldLeftMfoldLeftM适用于Foldable个实例的内容,但不包含Iterable,但我建议在使用Scalaz时避免Iterable。< / p>

所以你可以写:

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有一个固定的E类型别名,它就可以正常工作{1}}。)

现在,当您运行结果时,您将获得Writer[String, E \/ B],这与原始traverseLogged中的相同。您实际上可以在方法中执行此操作并直接返回Logged,但如果您需要编写此类多个操作,则保持LoggedOr级别可能会很有用。

这假设您实际上并不需要访问Writer中累积的f,但如果您这样做,则表明另一种类型可能更合适。< / p>