为什么延迟工厂方法在 F 的上下文中具有 return 值
Why is Deferred factory method has return value in the context of F
我正在查看 cats.effect.concurrent.Deferred
并注意到其伴随对象中的所有 pure 工厂方法 return 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
,也不清楚如何通过其 public 方法修改 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]
的引用,这正是保护我们的原因。
我正在查看 cats.effect.concurrent.Deferred
并注意到其伴随对象中的所有 pure 工厂方法 return 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
,也不清楚如何通过其 public 方法修改 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]
的引用,这正是保护我们的原因。