From 88bbc7734b71b76880c9dc60b08648d2883bcba1 Mon Sep 17 00:00:00 2001 From: Daniel Urban Date: Fri, 24 May 2024 02:07:47 +0200 Subject: [PATCH] Override fix in Defer instance, to avoid lazy val --- .../main/scala/dev/tauri/choam/core/Rxn.scala | 9 ++++++++ .../test/scala/dev/tauri/choam/RxnSpec.scala | 23 ++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/dev/tauri/choam/core/Rxn.scala b/core/shared/src/main/scala/dev/tauri/choam/core/Rxn.scala index 6cb8da2e..6c3f79ac 100644 --- a/core/shared/src/main/scala/dev/tauri/choam/core/Rxn.scala +++ b/core/shared/src/main/scala/dev/tauri/choam/core/Rxn.scala @@ -1603,6 +1603,15 @@ private sealed abstract class RxnInstances6 extends RxnInstances7 { self: Rxn.ty implicit final def deferInstance[X]: Defer[Rxn[X, *]] = new Defer[Rxn[X, *]] { final override def defer[A](fa: => Rxn[X, A]): Rxn[X, A] = self.computed[X, A] { x => fa.provide(x) } + final override def fix[A](fn: Rxn[X, A] => Rxn[X, A]): Rxn[X, A] = { + // This is in effect a "thread-unsafe lazy val"; + // we know exactly how `defer` works, so it's safe + // to do this here (and this way we avoid the + // synchronization of an actual lazy val): + val ref = new scala.runtime.ObjectRef[Rxn[X, A]](null) + ref.elem = fn(defer { ref.elem }) + ref.elem + } } } diff --git a/core/shared/src/test/scala/dev/tauri/choam/RxnSpec.scala b/core/shared/src/test/scala/dev/tauri/choam/RxnSpec.scala index 965d98bb..11fc68cc 100644 --- a/core/shared/src/test/scala/dev/tauri/choam/RxnSpec.scala +++ b/core/shared/src/test/scala/dev/tauri/choam/RxnSpec.scala @@ -21,7 +21,7 @@ import java.util.concurrent.atomic.AtomicInteger import scala.concurrent.duration._ -import cats.{ Applicative, Monad, StackSafeMonad, Align } +import cats.{ Applicative, Monad, StackSafeMonad, Align, Defer } import cats.arrow.{ ArrowChoice, FunctionK } import cats.data.Ior import cats.effect.IO @@ -1007,6 +1007,27 @@ trait RxnSpec[F[_]] extends BaseSpecAsyncF[F] { this: McasImplSpec => } yield () } + test("Defer instance") { + val inst = Defer[Rxn[Int, *]] + for { + ctr <- F.delay { new AtomicInteger } + rxn0 = inst.defer { + ctr.getAndIncrement() + Rxn.pure("result") + } + _ <- assertEqualsF(ctr.get(), 0) + _ <- assertResultF(rxn0[F](99), "result") + ref <- Ref.unpadded("-").run[F] + rxn1 = inst.fix[Int] { rec => + ref.unsafeCas("a", "b").as(42) + (Axn.unsafe.delay { + if (!ref.loc.unsafeCasV("-", "a")) { throw new AssertionError } + } *> Rxn.unsafe.retry) + rec + } + _ <- assertResultF(rxn1[F](99), 42) + _ <- assertResultF(ref.get.run[F], "b") + } yield () + } + test("Ref.Make instance") { val inst = implicitly[CatsRef.Make[Rxn[Int, *]]] val rxn = inst.refOf(42).flatMap { ref =>