我正在看 cats.effect.concurrent.Deferred
,并注意到所有 pure 其伴随对象内的工厂方法返回 F[Deferred[F, A]]
,而不仅仅是 Deferred[F, A]
def apply[F[_], A](implicit F: Concurrent[F]): F[Deferred[F, A]] =
F.delay(unsafe[F, A])
但是
/**
* Like `apply` but returns the newly allocated promise directly instead of wrapping it in `F.delay`.
* This method is considered unsafe because it is not referentially transparent -- it allocates
* mutable state.
*/
def unsafe[F[_]: Concurrent, A]: Deferred[F, A]
为什么?
abstract class
有两种定义的方法(省略了文档):
abstract class Deferred[F[_], A] {
def get: F[A]
def complete(a: A): F[Unit]
}
因此,即使我们直接分配Deferred
,也不清楚如何通过其公共方法修改Deferred
的状态。所有修改都用F[_]
悬挂。
问题不是在F
中悬挂突变,而是Deferred.unsafe
是否允许您编写不透明的代码。考虑以下两个程序:
import cats.effect.{ContextShift, IO}
import cats.effect.concurrent.Deferred
import cats.implicits._
import scala.concurrent.ExecutionContext
implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
val x = Deferred.unsafe[IO, Int]
val p1 = x.complete(1) *> x.get
val p2 = Deferred.unsafe[IO, Int].complete(1) *> Deferred.unsafe[IO, Int].get
这两个程序并不等于:p1
将计算1
,p2
将永远等待。我们可以构建这样的示例的事实表明,Deferred.unsafe
不是透明的 - 我们不能用参考自由地替换呼叫并最终得到等效程序。
如果我们尝试使用Deferred.apply
做同样的事情,我们会发现我们无法通过用值替换参考文献来提出成对的非等效程序。我们可以尝试一下:
val x = Deferred[IO, Int]
val p1 = x.flatMap(_.complete(1)) *> x.flatMap(_.get)
val p2 = Deferred[IO, Int].flatMap(_.complete(1)) *> Deferred[IO, Int].flatMap(_.get)
…但这给了我们两个等效的程序(都悬挂)。即使我们尝试这样的事情:
val x = Deferred[IO, Int]
val p3 = x.flatMap(x => x.complete(1) *> x.get)
…所有参考透明度都告诉我们,我们可以将该代码重写为以下:
val p4 = Deferred[IO, Int].flatMap(x => x.complete(1) *> x.get)
…等效于p3
,因此我们未能再次破坏参考透明度。
当我们使用Deferred.apply
时,我们无法在F
之外获得对可变的Deferred[IO, Int]
的引用,这是在此处特别保护我们的。