我正在做一些可能失败的计算,所以我使用了析取类型作为结果。我有一个相当常见的模式,我已经编写了以下函数:
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:我希望问题标题足够清晰> _<
答案 0 :(得分:2)
首先,值得注意的是,traverse
基本上是foldLeftM
。 foldLeftM
适用于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>