7

我正在查看cats.effect.concurrent.Deferred并注意到其伴随对象中的所有工厂方法都返回,而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[_]

4

1 回答 1

7

问题不在于突变是否被暂停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将计算1p2永远等待。我们可以构造这样一个示例的事实表明它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[IO, Int]的上下文之外获得对可变对象的引用这一事实特别是在这里保护我们的东西。FDeferred.apply

于 2019-02-25T13:13:56.970 回答