You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Cats Effect Ref实现缓存:为何内部Ref未按预期设置?

Cats Effect Ref 缓存未按预期设置值

尝试用函数式方式借助Cats Effect的Ref monad实现缓存,但执行后发现Ref内部值未被正确设置,设置操作完成后读取依然是None。

问题代码

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!

问题根源

问题出在cache的定义上:val cache: IO[Ref[IO, Option[String]]] = Ref.of(None)。这里的cache是一个IO操作,每次调用cache(比如在三个检查/设置步骤中)都会执行Ref.of,创建一个全新的Ref实例:

  • checkValueBeforeSet用的是第一个Ref(初始值None)
  • doSetAction用的是第二个全新的Ref,设置了值但和第一个无关
  • checkValueAfterSet用的是第三个全新的Ref(初始值还是None)

三次操作根本没共享同一个Ref,自然读不到设置后的值。

解决方案

只创建一次Ref实例,然后在整个流程中复用它:

import cats.effect.kernel.Ref
import cats.effect.{IO, IOApp}

object SomeMain extends IOApp.Simple {

  override def run: IO[Unit] = {
    // 仅初始化一次Ref实例
    Ref.of[IO, Option[String]](None).flatMap { cache =>
      val checkValueBeforeSet = cache.get.flatMap {
        case Some(v) => IO(println(v))
        case None => IO(println("as expected no value yet"))
      }

      val doSetAction = cache.set(Some("abc")).map(_ => println("set action done"))

      val checkValueAfterSet = cache.get.flatMap {
        case Some(v) => IO(println(v))
        case None => IO(println("unexpected still no value set!"))
      }

      for {
        _ <- checkValueBeforeSet
        _ <- doSetAction
        _ <- checkValueAfterSet
      } yield ()
    }
  }
}

修改后的输出会变成:

as expected no value yet
set action done
abc

所有操作共享同一个Ref实例,设置和读取的是同一个状态,就能得到预期结果。

内容的提问来源于stack exchange,提问作者jakstack

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 12:49:56