Почему Отложенный фабричный метод имеет возвращаемое значение в контексте 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
именно то, что защищает нас здесь.