Попытка реализовать кеширование функциональным способом с использованием монады Cats Effect Ref.
Почему внутренняя ссылка не установлена должным образом?
import cats.effect.kernel.Ref
import cats.effect.{IO, IOApp}
object SomeMain extends IOApp.Simple {
val cache: IO[Ref[IO, Option[String]]] = Ref.of[IO, Option[String]](None)
override def run: IO[Unit] = {
val checkValueBeforeSet = cache.flatMap(ref => ref.get.flatMap {
case Some(v) => IO(println(v))
case None => IO(println("as expected no value yet"))
})
val doSetAction = cache.flatMap(ref => ref.set(Some("abc"))).map(_ => println("set action done"))
val checkValueAfterSet = cache.flatMap(ref => ref.get.flatMap {
case Some(v) => IO(println(v))
case None => IO(println("unexpected still no value set!"))
})
for {
_ <- checkValueBeforeSet
_ <- doSetAction
_ <- checkValueAfterSet
} yield IO()
}
}
Выход:
as expected no value yet
set action done
unexpected still no value set!





Это не работает, потому что вы каждый раз создаёте Ref заново.
Этот:
val cache: IO[Ref[IO, Option[String]]] = Ref.of[IO, Option[String]](None)
такой же как:
val cache: () => AtomicReference[Option[String]] =
() => new AtomicReference(None)
(Если вы не понимаете семантику IO[A], вы всегда можете представить, что это () => A или () => Future[A], и тогда это имеет смысл).
Вы используете cache дважды (трижды) внутри какого-то вспомогательного метода, и эти методы создают локальный Ref, а затем позволяют забыть об этом.
Вам придется сохранить значение, возвращаемое cache, и передать его (например, с внедрением зависимостей), чтобы это работало:
import cats.effect.kernel.Ref
import cats.effect.{IO, IOApp}
object SomeMain extends IOApp.Simple {
val cache: IO[Ref[IO, Option[String]]] = Ref.of[IO, Option[String]](None)
override def run: IO[Unit] = cache.flatMap { ref =>
val checkValueBeforeSet = ref.get.flatMap {
case Some(v) => IO(println(v))
case None => IO(println("as expected no value yet"))
}
val doSetAction = ref.set(Some("abc"))).map(_ => println("set action done")
val checkValueAfterSet = ref.get.flatMap {
case Some(v) => IO(println(v))
case None => IO(println("unexpected still no value set!"))
}
for {
_ <- checkValueBeforeSet
_ <- doSetAction
_ <- checkValueAfterSet
} yield IO()
}
}
По сути, Ref — это оболочка AtomicReference, которая выполняет каждую операцию в IO. Он позволяет безопасно изменять состояние, но его создание само по себе небезопасно (IO[Ref[]] создает новое Ref каждый раз, когда вы компонуете его в программу), поэтому вам нужно обратить внимание на то, как вы компонуете его в программу.
В качестве альтернативы вы можете использовать .memoize или создать Ref с помощью какого-либо метода unsafe, но это отличный способ выстрелить себе в ногу в долгосрочной перспективе, если у вас не очень хорошая интуиция, что вы делаете.