Почему Отложенный фабричный метод имеет возвращаемое значение в контексте F - PullRequest
0 голосов
/ 25 февраля 2019

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

1 Ответ

0 голосов
/ 25 февраля 2019

Вопрос не в том, приостановлена ​​ли мутация в 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, являетсяв частности, что нас здесь защищает.

...