如何在 Scala 中使用 IO monad 实现短路

How to implement a short-circuit with IO monad in Scala

我使用标准的 IO monad。

在某些时候,我需要短路。在给定条件下,我不想 运行 以下 ios.

这是我的解决方案,但我发现它太冗长而且不够优雅:

  def shortCircuit[A](io: IO[A], continue: Boolean) =
    io.map(a => if (continue) Some(a) else None)

  for {
    a <- io
    b <- shortCircuit(io, a == 1)
    c <- shortCircuit(io, b.map(_ == 1).getOrElse(false))
    d <- shortCircuit(io, b.map(_ == 1).getOrElse(false))
    e <- shortCircuit(io, b.map(_ == 1).getOrElse(false))
  } yield …

例如,对于第 3、4 和 5 行,我需要重复相同的条件。

有没有更好的方法?

你实际上并没有在那里短路任何东西。你还是运行的IOs;你只是没有捕捉到价值。

此外,标准 IO monad 没有定义 filter(或 withFilter),因此您不能在 for-comprehension 中使用守卫。

现在,如果你想要你所说的(相同的逻辑,只是更干),你总是可以在 for comprehension 中分配一个临时变量:

for {
  a <- io
  b <- shortCircuit(io, a == 1)
  continue = b.map(_ == 1).getOrElse(false)
  c <- shortCircuit(io, continue)
  d <- shortCircuit(io, continue)
  e <- shortCircuit(io, continue)
} yield …

但如果你真的想短路,你将不得不以某种方式拆开外壳。这是一种可能性,假设您只想将所有内容打包到一个数组中,因此 return 类型很简单,并且您的 IO 伴随对象有一个 apply 方法,您可以使用它来创建仅 [=35] 的东西=]s 一个值:

io.flatMap(a =>
  if (a == 1) IO(() => Array(a))
  else io.flatMap(b =>
    if (b == 1) IO(() => Array(a,b))
    else for {
      c <- io
      d <- io
      e <- io
    } yield Array(a,b,c,d,e)
  )
)

如果您的 return 类型更复杂,您可能需要更加努力地指定类型。

FWIW,值得注意的是,将事物包裹在 monad 中所付出的代价;没有,相同的逻辑将是(例如):

io() match {
  case 1 => Array(1)
  case a => io() match {
    case 1 => Array(a, 1)
    case b => Array(a, b, io(), io(), io())
  }
}

如果您允许 return 秒,您将得到:

val a = io()
if (a == 1) return Array(a)
val b = io()
if (b == 1) return Array(a, b)
Array(a, b, io(), io(), io())

原则上也可以用额外的方法来装饰 IO monad,但标准的 withFilter 不起作用,所以你将无法使用 for-comprehension 语法糖.