Agent
Agent: Reference with effectful mutators
Agent is like cats-effect Ref but it allows effectful updates of referenced value. It also allows enqueuing of mutations without waiting for their completion and mutation of values by filter(partial function).
trait Agent[F[_], A] {
def get: F[A]
def updateM(f: A => F[A]): F[A]
def fireUpdateM(f: A => F[A]): F[Unit]
def modifyM[B](f: A => F[(B, A)]): F[B]
def updateSomeM(f: PartialFunction[A, F[A]]): F[A]
def modifySomeM[B](default: B)(f: PartialFunction[A, F[(B, A)]]): F[B]
}
Agent's companion object contains default implementation that can be built from cats-effect's Ref and Semaphore:
object Agent {
final case class SemRef[F[_]: Monad: Fire, A](ref: Ref[F, A], sem: Semaphore[F]) extends Agent[F, A] {
// impl code...
}
}
Creation
One can create Agent of some value with helper MakeAgent.
It allows different effects for creating and running Agent:
trait MakeAgent[I[_], F[_]] {
def agentOf[A](a: A): I[Agent[F, A]]
}
MakeAgent has a companion object that offers easier creation of Agent instances. Agent could also be constructed from tofu.concurrent's MakeRef and MakeSemaphore implicit instances.
object MakeAgent {
def apply[I[_], F[_]](implicit mkAgent: MakeAgent[I, F]): Applier[I, F] = // impl
implicit def byRefAndSemaphore[I[_]: FlatMap, F[_]: Monad: Fire](
implicit
refs: MakeRef[I, F],
sems: MakeSemaphore[I, F]
) = // impl
}
If you are using the same effect for creation and running you can use Agents type alias defined in tofu.concurrent package object.
package tofu
package object concurrent {
// other aliases...
type Agents[F[_]] = MakeAgent[F, F]
// other aliases...
}
Examples
Using Agents:
import cats.effect.Sync
import cats.implicits._
import cats.Monad
import cats.syntax.flatMap._
import tofu.common.Console
import tofu.concurrent.Agents
def example[F[_]: Agents: Sync: Monad: Console]: F[Unit] =
for {
_ <- Monad[F].unit
agent <- Agents[F].of(42)
newValue <- agent.updateM(a => Console[F].putStrLn(s"current value is $a") *> Monad[F].pure(a + 27))
_ <- Console[F].putStrLn(s"new value is $newValue") // new value is 69
} yield ()
Using MakeAgent:
import cats.effect.Sync
import cats.implicits._
import cats.Monad
import cats.syntax.flatMap._
import tofu.common.Console
import tofu.concurrent.{Agents, MakeAgent, MakeRef, MakeSemaphore, Refs, Semaphores}
import tofu.Fire
def example[F[_]: Agents: Fire: Monad: Console: Sync: Refs: Semaphores](
implicit
refs: MakeRef[Option, F],
sems: MakeSemaphore[Option, F]
): F[Unit] =
for {
_ <- Monad[F].unit
agent <- MakeAgent[Option, F].of(42).map(Monad[F].pure(_)).getOrElse(Agents[F].of(42))
newValue <- agent.updateM(a => Console[F].putStrLn(s"current value is $a") *> Monad[F].pure(a + 27))
_ <- Console[F].putStrLn(s"new value is $newValue") // new value is 69
} yield ()