Repository: 7mind/izumi
Pull request #1231 (2020-09-18 17:42:50)

Fix monix-bio BIOTemporal instance: count the time correctly, require cats-effect Timer instead of Clock2 (available in monix-bio by default); fix BIOTemporal instance resolution


Patch:
diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOFiber.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOFiber.scala
index a73d1b76c..b83bcae92 100644
--- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOFiber.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOFiber.scala
@@ -12,7 +12,7 @@ trait BIOFiber3[F[-_, +_, +_], +E, +A] {
 }
 
 object BIOFiber3 {
-  def fromZIO[E, A](f: Fiber[E, A]): BIOFiber3[ZIO, E, A] =
+  @inline def fromZIO[E, A](f: Fiber[E, A]): BIOFiber3[ZIO, E, A] =
     new BIOFiber3[ZIO, E, A] {
       override val join: IO[E, A] = f.join
       override val observe: IO[Nothing, BIOExit[E, A]] = f.await.map(ZIOExit.toBIOExit[E, A])
@@ -26,14 +26,14 @@ object BIOFiber3 {
       override def join: FR[Any, Throwable, A] = bioFiber.join
     }
   }
-
 }
 
 object BIOFiber {
   @inline def fromZIO[E, A](f: Fiber[E, A]): BIOFiber3[ZIO, E, A] = BIOFiber3.fromZIO(f)
-  @inline def fromMonix[E, A](f: bio.Fiber[E, A]): BIOFiber[bio.IO, E, A] = new BIOFiber[bio.IO, E, A] {
-    override val join: bio.IO[E, A] = f.join
-    override val observe: bio.IO[Nothing, BIOExit[E, A]] = f.join.redeemCause(c => fromMonixCause(c), a => BIOExit.Success(a))
-    override val interrupt: bio.IO[Nothing, Unit] = f.cancel.void
-  }
+  @inline def fromMonix[E, A](f: bio.Fiber[E, A]): BIOFiber[bio.IO, E, A] =
+    new BIOFiber[bio.IO, E, A] {
+      override val join: bio.IO[E, A] = f.join
+      override val observe: bio.IO[Nothing, BIOExit[E, A]] = f.join.redeemCause(c => fromMonixCause(c), a => BIOExit.Success(a))
+      override val interrupt: bio.IO[Nothing, Unit] = f.cancel.void
+    }
 }
diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOFork3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOFork3.scala
index 6c27b4889..883608e6c 100644
--- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOFork3.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOFork3.scala
@@ -1,5 +1,8 @@
 package izumi.functional.bio
 
+import izumi.functional.bio.LowPriorityBIOForkInstances._BIOForkMonixBIO
+import izumi.fundamentals.platform.language.unused
+import monix.bio.IO
 import zio.ZIO
 
 trait BIOFork3[F[-_, +_, +_]] extends BIOForkInstances {
@@ -21,6 +24,21 @@ object BIOForkInstances extends LowPriorityBIOForkInstances {
   }
 }
 
-sealed trait LowPriorityBIOForkInstances {
+sealed trait LowPriorityBIOForkInstances extends LowPriorityBIOForkInstances1 {
+  implicit def BIOForkMonix[BIOForkMonixBIO](implicit @unused M: _BIOForkMonixBIO[BIOForkMonixBIO]): BIOForkMonixBIO = {
+    new BIOFork[monix.bio.IO] {
+      override def fork[R, E, A](f: IO[E, A]): IO[Nothing, BIOFiber[monix.bio.IO, E, A]] = f.start.map(BIOFiber.fromMonix)
+    }.asInstanceOf[BIOForkMonixBIO]
+  }
+}
+
+object LowPriorityBIOForkInstances {
+  final abstract class _BIOForkMonixBIO[A]
+  object _BIOForkMonixBIO {
+    @inline implicit final def get: _BIOForkMonixBIO[BIOFork[monix.bio.IO]] = null
+  }
+}
+
+sealed trait LowPriorityBIOForkInstances1 {
   @inline implicit final def BIOFork3To2[FR[-_, +_, +_], R](implicit BIOFork3: BIOFork3[FR]): BIOFork[FR[R, +?, +?]] = cast3To2(BIOFork3)
 }
diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOTemporal3.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOTemporal3.scala
index f18a3e167..2f8313347 100644
--- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOTemporal3.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOTemporal3.scala
@@ -1,7 +1,8 @@
 package izumi.functional.bio
 
+import izumi.functional.bio.BIOTemporalInstancesLowPriority1.{_BIOTemporalMonixBIO, _TimerMonixUIO}
 import izumi.functional.bio.impl.{BIOTemporalMonix, BIOTemporalZio}
-import monix.bio
+import izumi.fundamentals.platform.language.unused
 import zio.ZIO
 
 import scala.concurrent.duration.{Duration, FiniteDuration}
@@ -30,10 +31,25 @@ trait BIOTemporal3[F[-_, +_, +_]] extends BIOAsync3[F] with BIOTemporalInstances
 }
 
 private[bio] sealed trait BIOTemporalInstances
-object BIOTemporalInstances {
+object BIOTemporalInstances extends BIOTemporalInstancesLowPriority1 {
   @inline implicit final def BIOTemporal3Zio(implicit clockService: zio.clock.Clock): BIOTemporal3[ZIO] = new BIOTemporalZio(clockService)
 }
 
 sealed trait BIOTemporalInstancesLowPriority1 {
-  @inline implicit final def BIOTemporal2Monix(implicit clock: Clock2[bio.IO]): BIOTemporal[bio.IO] = new BIOTemporalMonix(clock)
+  @inline implicit final def BIOTemporalMonix[BIOTemporalMonixBIO, TimerMonixUIO](
+    implicit
+    @unused A: _BIOTemporalMonixBIO[BIOTemporalMonixBIO],
+    @unused T: _TimerMonixUIO[TimerMonixUIO],
+    timer: TimerMonixUIO,
+  ): BIOTemporalMonixBIO = new BIOTemporalMonix(timer.asInstanceOf[cats.effect.Timer[monix.bio.UIO]]).asInstanceOf[BIOTemporalMonixBIO]
+}
+object BIOTemporalInstancesLowPriority1 {
+  final abstract class _BIOTemporalMonixBIO[A]
+  object _BIOTemporalMonixBIO {
+    @inline implicit final def get: _BIOTemporalMonixBIO[BIOTemporal[monix.bio.IO]] = null
+  }
+  final abstract class _TimerMonixUIO[A]
+  object _TimerMonixUIO {
+    @inline implicit final def get: _TimerMonixUIO[cats.effect.Timer[monix.bio.UIO]] = null
+  }
 }
diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BIOTemporalMonix.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BIOTemporalMonix.scala
index 007e2f56b..d19f064bd 100644
--- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BIOTemporalMonix.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/BIOTemporalMonix.scala
@@ -1,26 +1,29 @@
 package izumi.functional.bio.impl
 
-import izumi.functional.bio.{BIOTemporal, Clock2}
-import monix.bio.IO
+import java.util.concurrent.TimeUnit
+
+import cats.effect.Timer
+import izumi.functional.bio.BIOTemporal
+import monix.bio.{IO, UIO}
 
 import scala.concurrent.duration._
 
-class BIOTemporalMonix(clock: Clock2[IO]) extends BIOAsyncMonix with BIOTemporal[IO] {
+class BIOTemporalMonix(timer: Timer[UIO]) extends BIOAsyncMonix with BIOTemporal[IO] {
   @inline override def sleep(duration: Duration): IO[Nothing, Unit] = IO.sleep(FiniteDuration(duration.length, duration.unit))
   @inline override def retryOrElse[R, E, A, E2](r: IO[E, A])(duration: FiniteDuration, orElse: => IO[E2, A]): IO[E2, A] = {
-    val boundary = clock.now().map(_.toEpochSecond + duration.toSeconds)
-    def loop: IO[E2, A] = {
+    def loop(maxTime: Long): IO[E2, A] = {
       r.redeemCauseWith(
         _ =>
           for {
-            now <- clock.now()
-            time <- boundary
-            res <- if (now.toEpochSecond < time) loop else orElse
+            now <- timer.clock.monotonic(TimeUnit.NANOSECONDS)
+            res <- if (now < maxTime) loop(maxTime) else orElse
           } yield res,
         res => IO.pure(res),
       )
     }
-    loop
+    timer
+      .clock.monotonic(TimeUnit.NANOSECONDS).map(_ + duration.toNanos)
+      .flatMap(loop)
   }
 
   @inline override def timeout[R, E, A](r: IO[E, A])(duration: Duration): IO[E, Option[A]] = r.timeout(FiniteDuration(duration.length, duration.unit))


Test patch:
diff --git a/fundamentals/fundamentals-bio/src/test/scala/izumi/fundamentals/bio/test/BIOSyntaxTest.scala b/fundamentals/fundamentals-bio/src/test/scala/izumi/fundamentals/bio/test/BIOSyntaxTest.scala
index ca87e2e1c..eebb4b874 100644
--- a/fundamentals/fundamentals-bio/src/test/scala/izumi/fundamentals/bio/test/BIOSyntaxTest.scala
+++ b/fundamentals/fundamentals-bio/src/test/scala/izumi/fundamentals/bio/test/BIOSyntaxTest.scala
@@ -3,6 +3,7 @@ package izumi.fundamentals.bio.test
 import izumi.functional.bio.{BIO, BIOArrow, BIOAsk, BIOBifunctor3, BIOError, BIOFork, BIOFork3, BIOFunctor, BIOLocal, BIOMonad, BIOMonad3, BIOMonadAsk, BIOPrimitives, BIOPrimitives3, BIOProfunctor, BIOTemporal, BIOTemporal3, F}
 import monix.bio
 import org.scalatest.wordspec.AnyWordSpec
+import zio.Has
 
 import scala.concurrent.duration._
 
@@ -198,17 +199,20 @@ class BIOSyntaxTest extends AnyWordSpec {
       F.fork(F.mkRef(4).flatMap(r => r.update(_ + 5) *> r.get.map(_ - 1))).flatMap(_.join) *>
       F.mkRef(4).flatMap(r => r.update(_ + 5) *> r.get.map(_ - 1)).fork.flatMap(_.join)
     }
-    lazy val zioTest = (
-      x[zio.IO],
-      y[zio.IO](_: BIOTemporal[zio.IO]),
-      z[zio.IO],
-      `attach BIOPrimitives & BIOFork methods even when they aren't imported`[zio.IO],
-      `attach BIOPrimitives & BIOFork3 methods to a trifunctor BIO even when not imported`[zio.ZIO],
-    )
+    lazy val zioTest = {
+      implicit val zioClock: zio.clock.Clock = Has(zio.clock.Clock.Service.live)
+      (
+        x[zio.IO],
+        y[zio.IO],
+        z[zio.IO],
+        `attach BIOPrimitives & BIOFork methods even when they aren't imported`[zio.IO],
+        `attach BIOPrimitives & BIOFork3 methods to a trifunctor BIO even when not imported`[zio.ZIO],
+      )
+    }
 
     lazy val monixTest = (
       x[bio.IO],
-      y[bio.IO](_: BIOTemporal[bio.IO]),
+      y[bio.IO],
       z[bio.IO],
     )
     lazy val _ = (zioTest, monixTest)