问题描述
我正在尝试使用 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
实例,当 X
和 Y
都有一个.由于我们使用的是 IorNel
,X = 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:
x.map(f).sequence
相当于做x.traverse(f)
- 我们可以预先要求键非空,也可以返回非空结果.
后一步为我们提供了一个集合的 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 累积错误和成功的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!