1

假设我有两个计算结果列表

val a: List[ Throwable \/ A] = ...
val b: List[ Throwable \/ B] = ...

我有计算最终结果的函数,例如

def calc(a: A, b: B): Throwable \/ C = ...

我需要计算每个结果的所有结果,a并且如果有的话b还要累积。Throwable有没有像 Applicative style 这样的优雅方式<*>

UPDT:结果必须如下

val c: List[ Throwable \/ C] ..

我来到的最好的解决方案是

def combine[A, B, C](f: (A, B) => Throwable \/ C, a: Throwable \/ A, b: Throwable \/ B): List[Throwable] \/ C = (a, b) match{
    case ( -\/(errA), \/-(_)) => -\/(List(errA))
    case (\/-(_), -\/(errB)) => -\/(List(errB))
    case (-\/(errA), -\/(errB)) => -\/(List(errA, errB))
    case (\/-(valA), \/-(valB)) => f(valA, valB).leftMap( List(_))
  }

然后

val result = (a |@| b)(combine(calc, _, _))

但在那种情况下,我有一些额外的结果。结果列表有类型List[ List[Throwable] \/ C]

4

2 回答 2

3

我不确定我是否完全理解这个问题,但我会试一试,希望足够接近你可以推断。假设您有一个 type 值列表Throwable \/ A和另一个of 值列表,Throwable \/ B并且您希望将它们与函数成对组合(A, B) => Throwable \/ C成累积错误列表或Cs 列表。我会这样写:

import scalaz._, Scalaz._

def process[A, B, C](
  as: List[Throwable \/ A],
  bs: List[Throwable \/ B]
)(f: (A, B) => Throwable \/ C): NonEmptyList[Throwable] \/ List[C] = ???

有很多方法可以实现这一点,但基本思想是我们需要暂时将析取转换为验证,以便获得我们想要的错误累积(请参阅我的问题,了解为什么是必要的一些讨论) .

def process[A, B, C](
  as: List[Throwable \/ A],
  bs: List[Throwable \/ B]
)(f: (A, B) => Throwable \/ C): NonEmptyList[Throwable] \/ List[C] =
  as.zip(bs).traverseU {
    case (a, b) =>
      val validatedA: ValidationNel[Throwable, A] = a.validation.toValidationNel
      val validatedB: ValidationNel[Throwable, B] = b.validation.toValidationNel

      validatedA.tuple(validatedB).disjunction.flatMap {
        case (a, b) => f(a, b).leftMap(NonEmptyList(_))
      }.validation
  }.disjunction

在这里,我们将每对值转换为验证,使用 applicativetuple在累积错误的同时将它们组合,转换回析取以便我们可以绑定f,回到验证以便我们可以应用排序traverseU,然后返回析取.

(请注意,我假设列表具有相同的长度,或者您不介意忽略任何一个中的额外结果,但这只是为了简单起见——如果您想要其他行为,很容易调整。 )

于 2015-12-05T18:02:29.367 回答
0

最后我找到了一个合适的解决方案。这个想法是使用单子变压器EitherT

val a: List[Throwable \/ A] = ...
val b: List[Throwable \/ B] = ...
val aT = EitherT.eitherT(a)
// aT: scalaz.EitherT[List,Throwable, A] = ...
val bT = EitherT.either(b)
// bT: scalaz.EitherT[List,Throwable, B] = ...

接下来是魔术:

val result = (aT |@| bT)(calc( _, _ ))
// c: scalaz.EitherT[[+A]List[A],Throwable,C] = ...

在这里,我们像通常的 Applicative 样式函数调用一样,从 'a' 获取所有“好”* 值,从 'b' 获取所有“好”值。结果中没有额外的功能或额外的数据。

'*' - 术语“好”表示不是“可投掷”

于 2015-12-07T09:41:43.667 回答