Scala Cat 使用 Ior 累积错误和成功

Chr*_*ton 4 scala scala-cats

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

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 (_ |+| _)
}
Run Code Online (Sandbox Code Playgroud)

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

Ole*_*cov 5

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

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)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

让我们分解一下:

我们将尝试“翻转” aList[IorNel[Error, A]]IorNel[Error, List[A]]. 然而,从keys.map(find)我们得到List[F[IorNel[...]]],所以我们还需要首先以类似的方式“翻转”它。这可以通过.sequence对结果使用来完成,这就是力量F[_]: Applicative约束。

Applicative[Future]只要有隐含ExecutionContext的作用域,NB就可用。你也可以摆脱F和使用Future.sequence直接。

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

Ior[X, Y]有一个Semigroup实例时,双方XY有一个。由于我们正在使用IorNel, X = NonEmptyList[Z],这很满意。对于Y = A- 您的域类型 - 它可能不可用。

但是我们不想将所有结果组合成一个A我们想要的 ,Y = List[A](它也总是有一个半群)。所以,我们把IorNel[Error, A]我们拥有的每一个都map A带到一个单身人士List[A]

nelsList.map(nel => nel.map(List(_)))
Run Code Online (Sandbox Code Playgroud)

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


这可以通过做几件事来改善:

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

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

nelsList.map(nel => nel.map(List(_)))
Run Code Online (Sandbox Code Playgroud)

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

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))
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以在内部进行非空检查:

keys.traverse(find).map(_.reduceMap(_.map(NonEmptyList.one)))
Run Code Online (Sandbox Code Playgroud)