Почему Отложенный фабричный метод имеет возвращаемое значение в контексте F

Я смотрю на 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[_],

1 ответ

Решение

Вопрос не в том, приостановлена ​​ли мутация в 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[IO, Int] вне контекста F когда мы используем Deferred.apply именно то, что защищает нас здесь.

Другие вопросы по тегам