IOLocal provides a handy way of manipulating a context on different scopes.
In some scenarios, IOLocal can be considered as an alternative to cats.data.Kleisli.
IOLocal should not be treated as Ref, since the former abides different laws.
Once a fiber is forked, for example by Spawn[F].start
, the forked fiber manipulates the
copy of the parent's context. For example, two forked fibers will never see each other's
modifications to the same IOLocal, each fiber will only see its own modifications.
===Operations on IOLocal are visible to the fiber===
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Fiber A │ update(_ + 1) │ Fiber A │ update(_ + 1) │ Fiber A │
│ (local 42) │──────────────►│ (local 43) │──────────────►│ (local 44) │
└────────────┘ └────────────┘ └────────────┘
def inc(name: String, local: IOLocal[Int]): IO[Unit] =
local.update(_ + 1) >> local.get.flatMap(current => IO.println(s"fiber $$name: $$current"))
for {
local <- IOLocal(42)
_ <- inc(1, local)
_ <- inc(2, local)
current <- local.get
_ <- IO.println(s"fiber A: $$current")
} yield ()
// output:
// update 1: 43
// update 2: 44
// fiber A: 44
===A forked fiber operates on a copy of the parent IOLocal===
A '''forked''' fiber (i.e. via Spawn[F].start
) operates on a '''copy''' of the parent
IOLocal
. Hence, the children operations are not reflected on the parent context.
┌────────────┐ ┌────────────┐
fork │ Fiber B │ update(_ - 1) │ Fiber B │
┌─────►│ (local 42) │──────────────►│ (local 41) │
│ └────────────┘ └────────────┘
┌────────────┐─┘ ┌────────────┐
│ Fiber A │ │ Fiber A │
│ (local 42) │────────────────────────────────────►│ (local 42) │
└────────────┘─┐ └────────────┘
│ ┌────────────┐ ┌────────────┐
│ fork │ Fiber C │ update(_ + 1) │ Fiber C │
└─────►│ (local 42) │──────────────►│ (local 43) │
└────────────┘ └────────────┘
def update(name: String, local: IOLocal[Int], f: Int => Int): IO[Unit] =
local.update(f) >> local.get.flatMap(current => IO.println(s"$$name: $$current"))
for {
local <- IOLocal(42)
fiber1 <- update("fiber B", local, _ - 1).start
fiber2 <- update("fiber C", local, _ + 1).start
_ <- fiber1.joinWithNever
_ <- fiber2.joinWithNever
current <- local.get
_ <- IO.println(s"fiber A: $$current")
} yield ()
// output:
// fiber B: 41
// fiber C: 43
// fiber A: 42
===Parent operations on IOLocal are invisible to children===
┌────────────┐ ┌────────────┐
fork │ Fiber B │ update(_ + 1) │ Fiber B │
┌─────►│ (local 42) │──────────────►│ (local 43) │
│ └────────────┘ └────────────┘
┌────────────┐─┘ ┌────────────┐
│ Fiber A │ update(_ - 1) │ Fiber A │
│ (local 42) │────────────────────────────────────►│ (local 41) │
└────────────┘─┐ └────────────┘
│ ┌────────────┐ ┌────────────┐
│ fork │ Fiber C │ update(_ + 2) │ Fiber C │
└─────►│ (local 42) │──────────────►│ (local 44) │
└────────────┘ └────────────┘
def update(name: String, local: IOLocal[Int], f: Int => Int): IO[Unit] =
IO.sleep(1.second) >> local.update(f) >> local.get.flatMap(current => IO.println(s"$$name: $$current"))
for {
local <- IOLocal(42)
fiber1 <- update("fiber B", local, _ + 1).start
fiber2 <- update("fiber C", local, _ + 2).start
_ <- fiber1.joinWithNever
_ <- fiber2.joinWithNever
_ <- update("fiber A", local, _ - 1)
} yield ()
// output:
// fiber B: 43
// fiber C: 44
// fiber A: 41
- Type parameters:
- A
the type of the local value
- Companion:
- object