如何在 scalaz` \/ 符号中累积 Throwable
How to accumulate Throwable in scalaz` \/ notation
假设我有两个计算结果列表
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]
我不确定我是否完全理解这个问题,但我会尝试一下,希望足够接近,让您可以推断。假设您有一个 Throwable \/ A
类型的值列表和另一个 Throwable \/ B
类型的值列表,并且您希望将它们与函数 (A, B) => Throwable \/ C
成对组合成累积错误列表或C
秒。我会这样写:
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
进行应用排序,然后返回到析取。
(请注意,我假设列表具有相同的长度,或者您不介意忽略任一列表中的额外结果,但这只是为了简单起见——如果你想要其他行为。)
终于找到合适的解决办法了。这个想法是使用 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' 获取所有 "good"* 值,从 'b' 获取所有 "good" 值,就像通常的 Applicative 风格函数调用一样。结果中没有额外的功能或额外的数据。
'*' - 术语 "good" 表示不是 'Throwable'
假设我有两个计算结果列表
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]
我不确定我是否完全理解这个问题,但我会尝试一下,希望足够接近,让您可以推断。假设您有一个 Throwable \/ A
类型的值列表和另一个 Throwable \/ B
类型的值列表,并且您希望将它们与函数 (A, B) => Throwable \/ C
成对组合成累积错误列表或C
秒。我会这样写:
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
进行应用排序,然后返回到析取。
(请注意,我假设列表具有相同的长度,或者您不介意忽略任一列表中的额外结果,但这只是为了简单起见——如果你想要其他行为。)
终于找到合适的解决办法了。这个想法是使用 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' 获取所有 "good"* 值,从 'b' 获取所有 "good" 值,就像通常的 Applicative 风格函数调用一样。结果中没有额外的功能或额外的数据。
'*' - 术语 "good" 表示不是 'Throwable'