【问题标题】:How to accumulate Throwable in scalaz` \/ notation如何在scalaz`\/符号中累积Throwable
【发布时间】:2015-12-07 17:41:43
【问题描述】:

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

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

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

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

我需要计算每个ab 的所有结果,如果有的话,还需要累积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]

【问题讨论】:

    标签: scala functional-programming scalaz applicative either


    【解决方案1】:

    我不确定我是否完全理解这个问题,但我会试一试,希望足够接近让您推断。假设您有一个Throwable \/ A 类型的值列表和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] = ???
    

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

    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
    

    这里我们将每对值转换为验证,使用应用程序tuple 将它们组合起来同时累积错误,转换回析取以便我们可以与f 绑定,返回验证以便我们可以排序应用traverseU,然后返回析取。

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

    【讨论】:

    • 这个想法是在函数calc 中将来自a 的所有“好”值与来自b 的所有“好”值组合起来,因此结果将是List[Throwable \/ C] 并附加所有“来自a 的“坏”值和来自b 的所有“坏”值到最终结果列表。
    【解决方案2】:

    我终于找到了合适的解决方案。这个想法是使用 monad 转换器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] = ...
    

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多