Ref/MVar的有效更新



我想对MVarRef内部的值应用有效的计算,并在计算成功的情况下原子地更新它,或者在操作失败的情况下放回初始值(在MVar的情况下(/什么都不做(在Ref的情况下。

I。参考案例

val ref = Ref.of[IO, Int](0)
def foo(i: Int): IO[Int] = //... some effectual computation

不幸的是,由于原子性很重要,而Ref不提供compareAndSet操作,因此必须显式地实现它,这看起来并不吸引人。

II。MVar案例

MVar提供互斥语义,但问题是bracket不允许我们put计算值。这里有一个例子:

val mvar = MVar.of[IO, Int](0)
def foo(i: Int): IO[Int] = IO(i + 1)
for {
mvar <- mvar
i <- mvar.take.bracket(foo)(mvar.put) //puts back 0, not 1
} yield ()

至少对于MVarRef,是否有实现这种行为的方法?

UPD

我用MVar实现了它,但它看起来相当难看:

def updateAtomically(mvar: MVar[IO, Int], foo: Int => IO[Int]): IO[Int] = for {
i <- mvar.take
ii <- foo(i).onError{
case t => mvar.put(i)
}
_ <- mvar.put(ii)
} yield ii

您可以使用MonadError.redeemWith进行以下操作:

def updateAtomically(mvar: MVar[IO, Int], foo: Int => IO[Int]): IO[Int] =
for {
i  <- mvar.take
ii <- foo(0).redeemWith(_ => IO(i), ii => mvar.put(ii) *> IO(ii))
} yield ii

然后:

import cats.Applicative.ops.toAllApplicativeOps
import cats.effect.{ ExitCode, IO, IOApp }
import cats.effect.concurrent.MVar
object Foo extends IOApp {
def foo(i: Int): IO[Int] = IO(i + 1)
def fooBar(i: Int): IO[Int] = IO.raiseError(new RuntimeException("BOOM"))
def run(args: List[String]): IO[ExitCode] =
(for {
mvar <- MVar.of[IO, Int](0)
res  <- updateAtomically(mvar, foo)
_    <- IO(println(res))
} yield res).map(_ => ExitCode.Success)
}

收益率:

1

和:

def run(args: List[String]): IO[ExitCode] =
(for {
mvar <- MVar.of[IO, Int](0)
res  <- updateAtomically(mvar, fooBar)
_    <- IO(println(res))
} yield res).map(_ => ExitCode.Success)

收益率:

0

最新更新