为什么延迟工厂方法在F的上下文中具有返回值

Som*_*ame 6 functional-programming scala referential-transparency cats-effect

我看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])
Run Code Online (Sandbox Code Playgroud)

  /**
    * 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]
Run Code Online (Sandbox Code Playgroud)

为什么?

abstract class具有限定(文档略)两种方法:

abstract class Deferred[F[_], A] {
  def get: F[A]
  def complete(a: A): F[Unit]
}
Run Code Online (Sandbox Code Playgroud)

因此,即使我们Deferred直接分配,也不清楚如何Deferred通过其公共方法修改状态.所有修改均暂停F[_].

Tra*_*own 6

问题不在于是否暂停突变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
Run Code Online (Sandbox Code Playgroud)

这两个程序不等同: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)
Run Code Online (Sandbox Code Playgroud)

...但是这给了我们两个相同的程序(都挂起).即使我们尝试这样的事情:

val x = Deferred[IO, Int]

val p3 = x.flatMap(x => x.complete(1) *> x.get)
Run Code Online (Sandbox Code Playgroud)

...所有参考透明度告诉我们,我们可以将该代码重写为以下内容:

val p4 = Deferred[IO, Int].flatMap(x => x.complete(1) *> x.get)
Run Code Online (Sandbox Code Playgroud)

......这相当于p3,所以我们再也没有打破参考透明度.

事实上,我们无法在我们使用Deferred[IO, Int]的时间范围之外获得对可变性的引用,这特别是在这里保护我们的是什么.FDeferred.apply

  • 这样,任何创建封装可变状态的实例的方法(即使可变状态是通过在“F[_]”中挂起突变的方法访问的)都不能被认为是引用透明的。那是对的吗? (2认同)