我假设您想要所有错误和所有成功的结果。这是一个可能的实现:
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 约束的原因。
注意只要在范围内有隐式 ExecutionContext,Applicative[Future] 就可用。也可以去掉F,直接使用Future.sequence。
现在,我们有了F[List[IorNel[Error, A]]],所以我们想要将map 内部部分转换为我们得到的nelsList。你可能认为sequence 也可以在那里使用,但它不能——它具有“第一个错误时短路”的行为,所以我们会丢失所有成功的值。让我们尝试改用|+|。
Ior[X, Y] 有一个 Semigroup 实例,而 X 和 Y 都有一个。由于我们使用的是IorNel、X = NonEmptyList[Z],因此我们很满意。对于 Y = A - 您的域类型 - 它可能不可用。
但我们不想将所有结果合并到一个 A 中,我们想要 Y = List[A](它也总是有一个半群)。所以,我们将我们拥有的每一个 IorNel[Error, A] 和 map A 带到一个单例 List[A]:
nelsList.map(nel => nel.map(List(_)))
这给了我们List[IorNel[Error, List[A]],我们可以减少它。不幸的是,由于 Ior 没有Monoid,我们不能完全使用方便的语法。因此,对于 stdlib 集合,一种方法是使用 .reduceOption(_ |+| _).getOrElse(Nil.rightIor)。
这可以通过做一些事情来改善:
-
x.map(f).sequence 相当于做x.traverse(f)
- 我们可以预先要求键不为空,也可以返回非空结果。
后一步为我们提供了 Reducible 集合的实例,让我们通过 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))
}
}
}
当然,你可以用这个做一个单行:
keys.traverse(find).map(_.reduceMap(_.map(NonEmptyList.one)))
或者,你可以在里面做非空检查:
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])
}
}