Ref/MVar 的有效更新
Effectful update of Ref/MVar
我想对 MVar
或 Ref
中的值应用有效计算,并且 原子地 更新它以防计算成功或放回初始值(在MVar
的情况下)/什么都不做(在Ref
的情况下)以防操作失败。
我。参考案例
val ref = Ref.of[IO, Int](0)
def foo(i: Int): IO[Int] = //... some effectual computation
由于原子性很重要,而且 Ref
不幸地没有提供 compareAndSet
操作,所以它必须明确地实现,这看起来并不吸引人。
二. MVar-case
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 ()
有没有办法至少对 MVar
或 Ref
实现这种行为?
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
我想对 MVar
或 Ref
中的值应用有效计算,并且 原子地 更新它以防计算成功或放回初始值(在MVar
的情况下)/什么都不做(在Ref
的情况下)以防操作失败。
我。参考案例
val ref = Ref.of[IO, Int](0)
def foo(i: Int): IO[Int] = //... some effectual computation
由于原子性很重要,而且 Ref
不幸地没有提供 compareAndSet
操作,所以它必须明确地实现,这看起来并不吸引人。
二. MVar-case
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 ()
有没有办法至少对 MVar
或 Ref
实现这种行为?
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