本文介绍了Scala Cat 使用 Ior 累积错误和成功的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 Cats 数据类型 Ior 来累积使用服务的错误和成功(可能返回错误).

I am trying to use Cats datatype Ior to accumulate both errors and successes of using a service (which can return an error).

def find(key: String): F[Ior[NonEmptyList[Error], A]] = {
  (for {
      b <- service.findByKey(key)
    } yield b.rightIor[NonEmptyList[Error]])
  .recover {
      case e: Error => Ior.leftNel(AnotherError)
    }
}

def findMultiple(keys: List[String]): F[Ior[NonEmptyList[Error], List[A]]] = {
  keys map find reduce (_ |+| _)
}

我的困惑在于如何组合错误/成功.我正在尝试使用 Semigroup combine(中缀语法)进行组合,但没有成功.有一个更好的方法吗?任何帮助都会很棒.

My confusion lies in how to combine the errors/successes. I am trying to use the Semigroup combine (infix syntax) to combine with no success. Is there a better way to do this? Any help would be great.

推荐答案

我将假设您想要所有错误和所有成功结果.这是一个可能的实现:

I'm going to assume that you want both all errors and all successful results. Here's a possible implementation:

class Foo[F[_]: Applicative, A](find: String => F[IorNel[Error, A]]) {
  def findMultiple(keys: List[String]): F[IorNel[Error, List[A]]] = {
    keys.map(find).sequence.map { nelsList =>
      nelsList.map(nel => nel.map(List(_)))
        .reduceOption(_ |+| _).getOrElse(Nil.rightIor)
    }
  }
}

让我们分解一下:

我们将尝试将List[IorNel[Error, A]]翻转"为IorNel[Error, List[A]].但是,通过执行 keys.map(find) 我们得到 List[F[IorNel[...]]],所以我们还需要将它翻转"到一个类似的时尚第一.这可以通过对结果使用 .sequence 来完成,这就是强制 F[_]: Applicative 约束的原因.

We will be trying to "flip" a List[IorNel[Error, A]] into IorNel[Error, List[A]]. However, from doing keys.map(find) we get List[F[IorNel[...]]], so we need to also "flip" it in a similar fashion first. That can be done by using .sequence on the result, and is what forces F[_]: Applicative constraint.

注意Applicative[Future] 在作用域中存在隐式 ExecutionContext 时可用.也可以去掉F,直接使用Future.sequence.

N.B. Applicative[Future] is available whenever there's an implicit ExecutionContext in scope. You can also get rid of F and use Future.sequence directly.

现在,我们有F[List[IorNel[Error, A]]],所以我们要map内部来转换nelsList 我们得到了.您可能认为 sequence 也可以在那里使用,但它不能 - 它具有第一个错误时短路"的行为,因此我们会丢失所有成功的值.让我们尝试使用 |+| 代替.

Now, we have F[List[IorNel[Error, A]]], so we want to map the inner part to transform the nelsList we got. You might think that sequence could be used there too, but it can not - it has the "short-circuit on first error" behavior, so we'd lose all successful values. Let's try to use |+| instead.

Ior[X, Y] 有一个 Semigroup 实例,当 XY 都有一个.由于我们使用的是 IorNelX = NonEmptyList[Z],这就满足了.对于 Y = A - 您的域类型 - 它可能不可用.

Ior[X, Y] has a Semigroup instance when both X and Y have one. Since we're using IorNel, X = NonEmptyList[Z], and that is satisfied. For Y = A - your domain type - it might not be available.

但我们不想将所有结果组合成一个 A,我们想要 Y = List[A](它也总是有一个半群).因此,我们将我们拥有的每个 IorNel[Error, A]map A 转换为一个单例 List[A]>:

But we don't want to combine all results into a single A, we want Y = List[A] (which also always has a semigroup). So, we take every IorNel[Error, A] we have and map A to a singleton List[A]:

nelsList.map(nel => nel.map(List(_)))

这给了我们List[IorNel[Error, List[A]],我们可以减少它.不幸的是,由于 Ior 没有 Monoid,我们不能很方便地使用语法.因此,对于 stdlib 集合,一种方法是执行 .reduceOption(_ |+| _).getOrElse(Nil.rightIor).

This gives us List[IorNel[Error, List[A]], which we can reduce. Unfortunately, since Ior does not have a Monoid, we can't quite use convenient syntax. So, with stdlib collections, one way is to do .reduceOption(_ |+| _).getOrElse(Nil.rightIor).

这可以通过做一些事情来改善:

This can be improved by doing few things:

  1. x.map(f).sequence 相当于做 x.traverse(f)
  2. 我们可以预先要求键非空,也可以返回非空结果.

后一步为我们提供了一个集合的 Reducible 实例,让我们通过执行 reduceMap

The latter step gives us Reducible instance for a collection, letting us shorten everything by doing reduceMap

class Foo2[F[_]: Applicative, A](find: String => F[IorNel[Error, A]]) {
  def findMultiple(keys: NonEmptyList[String]): F[IorNel[Error, NonEmptyList[A]]] = {
    keys.traverse(find).map { nelsList =>
      nelsList.reduceMap(nel => nel.map(NonEmptyList.one))
    }
  }
}

当然,你可以用这个做一个单线:

Of course, you can make a one-liner out of this:

keys.traverse(find).map(_.reduceMap(_.map(NonEmptyList.one)))

或者,你可以在里面做非空检查:

Or, you can do the non-emptiness check inside:

class Foo3[F[_]: Applicative, A](find: String => F[IorNel[Error, A]]) {
  def findMultiple(keys: List[String]): F[IorNel[Error, List[A]]] = {
    NonEmptyList.fromList(keys)
      .map(_.traverse(find).map { _.reduceMap(_.map(List(_))) })
      .getOrElse(List.empty[A].rightIor.pure[F])
  }
}

这篇关于Scala Cat 使用 Ior 累积错误和成功的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-05 17:22