对猫效应感到困惑 Async.memoize
Confused about cats-effect Async.memoize
我对猫效应还很陌生,但我想我已经掌握了它。但是我遇到了一种情况,我想记住 IO 的结果,但它并没有按照我的预期进行。
我要记忆的函数转换String => String,但是转换需要网络调用,所以实现为函数String => IO[String]。在非 IO 世界中,我会简单地保存调用的结果,但定义函数实际上无法访问它,因为它直到稍后才执行。如果我保存构造的 IO[String],它实际上不会有帮助,因为每次使用时 IO 都会重复网络调用。因此,我尝试使用 Async.memoize,它具有以下文档:
Lazily memoizes f. For every time the returned F[F[A]] is bound, the
effect f will be performed at most once (when the inner F[A] is bound
the first time).
我对 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 存储在地图中之前将其展平,但这并没有太大作用)。
考虑以下示例
给定以下辅助方法
def plus1(num: Int): IO[IO[Int]] = {
IO(IO(println("plus1")) *> IO(num + 1))
}
def mPlus1(num: Int): IO[IO[Int]] = {
Async.memoize(plus1(num).flatten)
}
让我们构建一个计算 plus1(1)
两次的程序。
val program1 = for {
io <- plus1(1)
_ <- io
_ <- io
} yield {}
program1.unsafeRunSync()
这会产生两次打印 plus1
的预期输出。
如果您使用 mPlus1
方法进行相同的操作
val program2 = for {
io <- mPlus1(1)
_ <- io
_ <- io
} yield {}
program2.unsafeRunSync()
它将打印 plus1
一次,确认记忆正在工作。
memoization 的诀窍是它应该只被评估一次才能达到预期的效果。现在考虑以下突出显示它的程序。
val memIo = mPlus1(1)
val program3 = for {
io1 <- memIo
io2 <- memIo
_ <- io1
_ <- io2
} yield {}
program3.unsafeRunSync()
并且它输出 plus1
两次,因为 io1
和 io2
是分开记忆的。
对于您的示例,foo
被打印一次,因为您正在使用地图并在未找到地图时更新值,而且这种情况只发生一次。每次评估 IO
时都会打印 bar
,因为您通过调用 res.flatten
.
失去了记忆效果
我对猫效应还很陌生,但我想我已经掌握了它。但是我遇到了一种情况,我想记住 IO 的结果,但它并没有按照我的预期进行。
我要记忆的函数转换String => String,但是转换需要网络调用,所以实现为函数String => IO[String]。在非 IO 世界中,我会简单地保存调用的结果,但定义函数实际上无法访问它,因为它直到稍后才执行。如果我保存构造的 IO[String],它实际上不会有帮助,因为每次使用时 IO 都会重复网络调用。因此,我尝试使用 Async.memoize,它具有以下文档:
Lazily memoizes f. For every time the returned F[F[A]] is bound, the effect f will be performed at most once (when the inner F[A] is bound the first time).
我对 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 存储在地图中之前将其展平,但这并没有太大作用)。
考虑以下示例
给定以下辅助方法
def plus1(num: Int): IO[IO[Int]] = {
IO(IO(println("plus1")) *> IO(num + 1))
}
def mPlus1(num: Int): IO[IO[Int]] = {
Async.memoize(plus1(num).flatten)
}
让我们构建一个计算 plus1(1)
两次的程序。
val program1 = for {
io <- plus1(1)
_ <- io
_ <- io
} yield {}
program1.unsafeRunSync()
这会产生两次打印 plus1
的预期输出。
如果您使用 mPlus1
方法进行相同的操作
val program2 = for {
io <- mPlus1(1)
_ <- io
_ <- io
} yield {}
program2.unsafeRunSync()
它将打印 plus1
一次,确认记忆正在工作。
memoization 的诀窍是它应该只被评估一次才能达到预期的效果。现在考虑以下突出显示它的程序。
val memIo = mPlus1(1)
val program3 = for {
io1 <- memIo
io2 <- memIo
_ <- io1
_ <- io2
} yield {}
program3.unsafeRunSync()
并且它输出 plus1
两次,因为 io1
和 io2
是分开记忆的。
对于您的示例,foo
被打印一次,因为您正在使用地图并在未找到地图时更新值,而且这种情况只发生一次。每次评估 IO
时都会打印 bar
,因为您通过调用 res.flatten
.