Consider the following simple F-algebra
trait Test[F[_]] {
def add(i: Int): F[Unit]
}
I want to provide implementation of it that tracks all of the added values and adds those that have not been added yet. It has to be done in a thread safe manner.
The "best" implementation I could come up with is using MVar[F, Ref[F, List[Int]]]
. Here is how it looks like
def statefulMutexTest[F[_]: Concurrent] = {
val mvarRef: F[MVar[F, Ref[F, List[Int]]]] =
for {
ref <- Ref.of[F, List[Int]](List.empty[Int])
mvar <- MVar.of[F, Ref[F, List[Int]]](ref)
} yield mvar
mvarRef map { mvar =>
new Test[F] {
override def add(i: Int): F[Unit] =
mvar.take.bracket(ref =>
for {
list <- ref.get
_ <- if (!list.contains(i)) ref.set(list :+ i) else Applicative[F].unit
} yield ())(mvar.put)
}
}
}
It looks pretty messy, but works as expected. I initially thought of using StateT
, but I don't like the idea that statefulness is exported to clients.
Basically Ref
is everything I would normally use for such case and Test
here is just a domain-specific wrapper:
object Test {
def of[F[_]: Sync]: F[Test[F]] =
Ref.of[F, Set[Int]](Set.empty).map(ref => (i: Int) => ref.update(_ + i).void)
}