如何在 Scala Cats Effect 中的 List[IO[Boolean]] 中遇到第一个“true”时返回

cod*_*oor 5 scala exists findfirst scala-cats cats-effect

假设我有一组规则,其中有一个IO[Boolean]在运行时返回的验证函数。

case class Rule1() {
  def validate(): IO[Boolean] = IO.pure(false)
}
case class Rule2() {
  def validate(): IO[Boolean] = IO.pure(false)
}
case class Rule3() {
  def validate(): IO[Boolean] = IO.pure(true)
}

val rules = List(Rule1(), Rule2(), Rule3())
Run Code Online (Sandbox Code Playgroud)

现在我必须迭代这些规则并查看“这些规则中的任何一个是否有效”,如果不有效则抛出异常!

for {
  i <- rules.map(_.validate()).sequence
  _ <- if (i.contains(true)) IO.unit else IO.raiseError(new RuntimeException("Failed"))
} yield ()
Run Code Online (Sandbox Code Playgroud)

上面代码片段的问题在于它试图评估所有规则!我真正想要的是在第一次验证时退出true

不知道如何在 Scala 中使用猫效果来实现这一点。

And*_*kin 6

我声称这existsM是实现你想要的最直接的方法。它的行为与 几乎相同exists,但对于一元谓词:

for {
  t <- rules.existsM(_.validate())
  _ <- IO.raiseUnless(t)(new RuntimeException("Failed"))
} yield ()
Run Code Online (Sandbox Code Playgroud)

一旦找到第一个,它也会停止搜索true

raiseUnless只是一些语法糖,相当于if-else你的问题中的。


Mat*_*zok 5

如果您查看 IDE 中可用扩展方法的列表,您可以找到findM

for {
  opt <- rules.findM(_.validate())
  _ <- opt match {
    case Some(_) => IO.unit
    case None    => IO.raiseError(new RuntimeException("Failed")
  }
} yield ()
Run Code Online (Sandbox Code Playgroud)

foldLeft可以使用和手动执行此操作flatMap

rules.foldLeft(IO.pure(false)) { (valueSoFar, nextValue) =>
  valueSoFar.flatMap {
    case true  => IO.pure(true)        // can skip evaluating nextValue 
    case false => nextValue.validate() // need to find the first true IO yet
  }
}.flatMap {
  case true  => IO.unit
  case false => IO.raiseError(new RuntimeException("Failed")
}
Run Code Online (Sandbox Code Playgroud)

前者应该具有额外的优点,即在找到第一个匹配项时不必遍历整个集合,而后者仍将遍历所有项目,即使会在某个时候开始丢弃它们。findM通过tailRecM在内部使用在第一次满足条件时终止迭代来解决这个问题。