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