我对猫的影响还很陌生,但我想我已经掌握了它。但是我遇到了一种情况,我想记住 IO 的结果,但它并没有达到我的预期。
我要记忆的函数转换String => String,但是转换需要网络调用,所以实现为函数String => IO[String]。在非 IO 世界中,我只是保存调用的结果,但定义函数实际上无法访问它,因为它直到稍后才会执行。如果我保存构造的 IO[String],它实际上不会有帮助,因为该 IO 会在每次使用时重复网络调用。因此,我尝试使用 Async.memoize,它具有以下文档:
懒惰地记住 f。每次绑定返回的 F[F[A]] 时,效果 f 最多执行一次(内部 F[A] 第一次绑定时)。
我对 memoize 的期望是一个对给定输入只执行一次的函数,并且返回的 IO 的内容只被评估一次;换句话说,我希望生成的 IO 表现得好像它是 IO.pure(result),除了第一次。但这似乎不是正在发生的事情。相反,我发现虽然被调用的函数本身只执行一次,但每次仍会评估 IO 的内容——就像我试图天真地保存和重用 IO 时会发生的那样。
我构建了一个示例来说明问题:
def plus1(num: Int): IO[Int] = {
println("foo")
IO(println("bar")) *> IO(num + 1)
}
var fooMap = Map[Int, IO[IO[Int]]]()
def mplus1(num: Int): IO[Int] = {
val check = fooMap.get(num)
val res = check.getOrElse {
val plus = Async.memoize(plus1(num))
fooMap = fooMap + ((num, plus))
plus
}
res.flatten
}
println("start")
val call1 = mplus1(2)
val call2 = mplus1(2)
val result = (call1 *> call2).unsafeRunSync()
println(result)
println(fooMap.toString)
println("finish")
这个程序的输出是:
start
foo
bar
bar
3
Map(2 -> <function1>)
finish
虽然 plus1 函数本身只执行一次(打印了一个“foo”),但 IO 中包含的输出“bar”被打印了两次,而我希望它也只打印一次。(我还尝试在将 Async.memoize 返回的 IO 存储到地图之前将其展平,但这并没有多大作用)。