Repository: 7mind/izumi (scala)

Issue:
Add Cats Parallel
In #1028 we have added `BIOParallel`. This gives us all the necessary components to implement `cats.Parallel`

Pull request description:
closes #1034

Patch:
diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/BIO3Syntax.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/BIO3Syntax.scala
index 4ae231fedd..6b1f0e65f2 100644
--- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/BIO3Syntax.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/BIO3Syntax.scala
@@ -2,7 +2,7 @@ package izumi.functional.bio.syntax
 
 import cats.data.Kleisli
 import izumi.functional.bio.syntax.BIO3Syntax.BIO3ImplicitPuns
-import izumi.functional.bio.{BIO3, BIOApplicative3, BIOArrow, BIOArrowChoice, BIOAsk, BIOAsync3, BIOBifunctor3, BIOBracket3, BIOError3, BIOExit, BIOFiber3, BIOFork3, BIOFunctor3, BIOGuarantee3, BIOLocal, BIOMonad3, BIOMonadError3, BIOPanic3, BIOProfunctor, BIOTemporal3}
+import izumi.functional.bio.{BIO3, BIOApplicative3, BIOArrow, BIOArrowChoice, BIOAsk, BIOAsync3, BIOBifunctor3, BIOBracket3, BIOError3, BIOExit, BIOFiber3, BIOFork3, BIOFunctor3, BIOGuarantee3, BIOLocal, BIOMonad3, BIOMonadError3, BIOPanic3, BIOProfunctor, BIOTemporal3, BIOParallel3}
 
 import scala.concurrent.duration.{Duration, FiniteDuration}
 import scala.language.implicitConversions
@@ -154,7 +154,16 @@ object BIO3Syntax {
       F.bracket(r: FR[R1, E1, A])(c => F.sync(c.close()))(use)
   }
 
-  class BIOAsync3Ops[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: BIOAsync3[FR]) extends BIO3Ops(r) {
+  trait BIOParallel3Ops[FR[-_ ,+_ , +_], -R, +E, +A] {
+    protected[this] val r: FR[R, E, A]
+    implicit protected[this] val F: BIOParallel3[FR]
+    @inline final def zipWithPar[R1 <: R, E1 >: E, B, C](that: FR[R1, E1, B])(f: (A, B) => C): FR[R1, E1, C] = F.zipWithPar(r, that)(f)
+    @inline final def zipPar[R1 <: R, E1 >: E, B](that: FR[R1, E1, B]): FR[R1, E1, (A, B)] = F.zipPar(r, that)
+    @inline final def zipParLeft[R1 <: R, E1 >: E, B](that: FR[R1, E1, B]): FR[R1, E1, A] = F.zipParLeft(r, that)
+    @inline final def zipParRight[R1 <: R, E1 >: E, B](that: FR[R1, E1, B]): FR[R1, E1, B] = F.zipParRight(r, that)
+  }
+
+  class BIOAsync3Ops[FR[-_, +_, +_], -R, +E, +A](override protected[this] val r: FR[R, E, A])(implicit override protected[this] val F: BIOAsync3[FR]) extends BIO3Ops(r) with BIOParallel3Ops[FR, R, E, A] {
     @inline final def race[R1 <: R, E1 >: E, A1 >: A](that: FR[R1, E1, A1]): FR[R1, E1, A1] = F.race(r, that)
   }
 
@@ -208,6 +217,13 @@ object BIO3Syntax {
     @inline final def BIOAsync3[FR[-_, +_, +_]: BIOAsync3]: BIOAsync3[FR] = implicitly
   }
   trait BIO3ImplicitPuns2 extends BIO3ImplicitPuns3 {
+    @inline implicit final def BIOParallel3[FR[-_, +_, +_]: BIOParallel3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOParallel3Ops[FR, R, E, A] = new BIO3Syntax.BIOParallel3Ops[FR, R, E, A]{
+      val F: BIOParallel3[FR] = BIOParallel3
+      val r: FR[R,E,A] = self
+    }
+    @inline final def BIOParallel3[FR[-_, +_, +_]: BIOParallel3]: BIOParallel3[FR] = implicitly
+  }
+  trait BIO3ImplicitPuns3 extends BIO3ImplicitPuns4 {
     @inline implicit final def BIO3[FR[-_, +_, +_]: BIO3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIO3Ops[FR, R, E, A] = new BIO3Syntax.BIO3Ops[FR, R, E, A](self)
     /**
       * Shorthand for [[BIO3#syncThrowable]]
@@ -219,44 +235,44 @@ object BIO3Syntax {
     @inline final def BIO3[FR[-_, +_, +_], A](effect: => A)(implicit F: BIO3[FR]): FR[Any, Throwable, A] = F.syncThrowable(effect)
     @inline final def BIO3[FR[-_, +_, +_]: BIO3]: BIO3[FR] = implicitly
   }
-  trait BIO3ImplicitPuns3 extends BIO3ImplicitPuns4 {
+  trait BIO3ImplicitPuns4 extends BIO3ImplicitPuns5 {
     @inline implicit final def BIOPanic3[FR[-_, +_, +_]: BIOPanic3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOPanic3Ops[FR, R, E, A] = new BIO3Syntax.BIOPanic3Ops[FR, R, E, A](self)
     @inline final def BIOPanic3[FR[-_, +_, +_]: BIOPanic3]: BIOPanic3[FR] = implicitly
   }
-  trait BIO3ImplicitPuns4 extends BIO3ImplicitPuns5 {
+  trait BIO3ImplicitPuns5 extends BIO3ImplicitPuns6 {
     @inline implicit final def BIOBracket3[FR[-_, +_, +_]: BIOBracket3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOBracket3Ops[FR, R, E, A] = new BIO3Syntax.BIOBracket3Ops[FR, R, E, A](self)
     @inline final def BIOBracket3[FR[-_, +_, +_]: BIOBracket3]: BIOBracket3[FR] = implicitly
   }
-  trait BIO3ImplicitPuns5 extends BIO3ImplicitPuns6 {
+  trait BIO3ImplicitPuns6 extends BIO3ImplicitPuns7 {
     @inline implicit final def BIOMonadError3[FR[-_, +_, +_]: BIOMonadError3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOMonadError3Ops[FR, R, E, A] = new BIO3Syntax.BIOMonadError3Ops[FR, R, E, A](self)
     @inline final def BIOMonadError3[FR[-_, +_, +_]: BIOMonadError3]: BIOMonadError3[FR] = implicitly
   }
-  trait BIO3ImplicitPuns6 extends BIO3ImplicitPuns7 {
+  trait BIO3ImplicitPuns7 extends BIO3ImplicitPuns8 {
     @inline implicit final def BIOError3[FR[-_, +_, +_]: BIOError3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOError3Ops[FR, R, E, A] = new BIO3Syntax.BIOError3Ops[FR, R, E, A](self)
     @inline final def BIOError3[FR[-_, +_, +_]: BIOError3]: BIOError3[FR] = implicitly
   }
-  trait BIO3ImplicitPuns7 extends BIO3ImplicitPuns8 {
+  trait BIO3ImplicitPuns8 extends BIO3ImplicitPuns9 {
     @inline implicit final def BIOGuarantee3[FR[-_, +_, +_]: BIOGuarantee3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOGuarantee3Ops[FR, R, E, A] = new BIO3Syntax.BIOGuarantee3Ops[FR, R, E, A](self)
     @inline final def BIOGuarantee3[FR[-_, +_, +_]: BIOGuarantee3]: BIOGuarantee3[FR] = implicitly
   }
-  trait BIO3ImplicitPuns8 extends BIO3ImplicitPuns9 {
+  trait BIO3ImplicitPuns9 extends BIO3ImplicitPuns10 {
     @inline implicit final def BIOMonad3[FR[-_, +_, +_]: BIOMonad3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOMonad3Ops[FR, R, E, A] = new BIO3Syntax.BIOMonad3Ops[FR, R, E, A](self)
     @inline final def BIOMonad3[FR[-_, +_, +_]: BIOMonad3]: BIOMonad3[FR] = implicitly
   }
-  trait BIO3ImplicitPuns9 extends BIO3ImplicitPuns10 {
+  trait BIO3ImplicitPuns10 extends BIO3ImplicitPuns11 {
     @inline implicit final def BIOApplicative3[FR[-_, +_, +_]: BIOApplicative3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOApplicative3Ops[FR, R, E, A] = new BIO3Syntax.BIOApplicative3Ops[FR, R, E, A](self)
     @inline final def BIOApplicative3[FR[-_, +_, +_]: BIOApplicative3]: BIOApplicative3[FR] = implicitly
   }
-  trait BIO3ImplicitPuns10 extends BIO3ImplicitPuns11 {
+  trait BIO3ImplicitPuns11 extends BIO3ImplicitPuns12 {
     @inline implicit final def BIOBifunctor3[FR[-_, +_, +_]: BIOBifunctor3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOBifunctor3Ops[FR, R, E, A] = new BIO3Syntax.BIOBifunctor3Ops[FR, R, E, A](self)
     @inline implicit final def BIOBifunctor3[FR[-_, +_, +_]: BIOFunctor3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOFunctor3Ops[FR, R, E, A] = new BIO3Syntax.BIOFunctor3Ops[FR, R, E, A](self)
     @inline final def BIOBifunctor3[FR[-_, +_, +_]: BIOBifunctor3]: BIOBifunctor3[FR] = implicitly
   }
-  trait BIO3ImplicitPuns11 extends BIOImplicitPuns12 {
+  trait BIO3ImplicitPuns12 extends BIOImplicitPuns13 {
     @inline implicit final def BIOFunctor3[FR[-_, +_, +_]: BIOFunctor3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOFunctor3Ops[FR, R, E, A] = new BIO3Syntax.BIOFunctor3Ops[FR, R, E, A](self)
     @inline final def BIOFunctor3[FR[-_, +_, +_]: BIOFunctor3]: BIOFunctor3[FR] = implicitly
   }
-  trait BIOImplicitPuns12 extends BIOImplicitPuns13 {
+  trait BIOImplicitPuns13 extends BIOImplicitPuns14 {
     // Note, as long as these auxilary conversions to BIOMonad/Applicative/Functor syntaxes etc.
     // have the same output type as BIOMonad3/etc conversions above, they will avoid the specificity rule
     // and _will not_ clash (because the outputs are equal, not <:<).
@@ -267,25 +283,25 @@ object BIO3Syntax {
     @inline implicit final def BIOLocal[FR[-_, +_, +_]: BIOLocal, R, E, A](self: FR[R, E, A])(implicit d1: DummyImplicit): BIO3Syntax.BIOLocalOpsKleisliSyntax[FR, R, E, A] = new BIO3Syntax.BIOLocalOpsKleisliSyntax[FR, R, E, A](self)
     @inline final def BIOLocal[FR[-_, +_, +_]: BIOLocal]: BIOLocal[FR] = implicitly
   }
-  trait BIOImplicitPuns13 extends BIOImplicitPuns14 {
+  trait BIOImplicitPuns14 extends BIOImplicitPuns15 {
     @inline implicit final def BIOMonadAsk[FR[-_, +_, +_]: BIOMonad3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOMonad3Ops[FR, R, E, A] = new BIO3Syntax.BIOMonad3Ops[FR, R, E, A](self)
     @inline final def BIOMonadAsk[FR[-_, +_, +_]: BIOAsk]: BIOAsk[FR] = implicitly
   }
-  trait BIOImplicitPuns14 extends BIOImplicitPuns15 {
+  trait BIOImplicitPuns15 extends BIOImplicitPuns16 {
     @inline implicit final def BIOAsk[FR[-_, +_, +_]: BIOApplicative3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOApplicative3Ops[FR, R, E, A] = new BIO3Syntax.BIOApplicative3Ops[FR, R, E, A](self)
     @inline final def BIOAsk[FR[-_, +_, +_]: BIOAsk]: BIOAsk[FR] = implicitly
   }
-  trait BIOImplicitPuns15 extends BIOImplicitPuns16 {
+  trait BIOImplicitPuns16 extends BIOImplicitPuns17 {
     @inline implicit final def BIOArrowChoice[FR[-_, +_, +_]: BIOArrowChoice, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOArrowChoiceOps[FR, R, E, A] = new BIO3Syntax.BIOArrowChoiceOps[FR, R, E, A](self)
     @inline implicit final def BIOArrowChoice[FR[-_, +_, +_]: BIOFunctor3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOFunctor3Ops[FR, R, E, A] = new BIO3Syntax.BIOFunctor3Ops[FR, R, E, A](self)
     @inline final def BIOArrowChoice[FR[-_, +_, +_]: BIOArrowChoice]: BIOArrowChoice[FR] = implicitly
   }
-  trait BIOImplicitPuns16 extends BIOImplicitPuns17 {
+  trait BIOImplicitPuns17 extends BIOImplicitPuns18 {
     @inline implicit final def BIOArrow[FR[-_, +_, +_]: BIOArrow, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOArrowOps[FR, R, E, A] = new BIO3Syntax.BIOArrowOps[FR, R, E, A](self)
     @inline implicit final def BIOArrow[FR[-_, +_, +_]: BIOFunctor3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOFunctor3Ops[FR, R, E, A] = new BIO3Syntax.BIOFunctor3Ops[FR, R, E, A](self)
     @inline final def BIOArrow[FR[-_, +_, +_]: BIOArrow]: BIOArrow[FR] = implicitly
   }
-  trait BIOImplicitPuns17 {
+  trait BIOImplicitPuns18 {
     @inline implicit final def BIOProfunctor[FR[-_, +_, +_]: BIOProfunctor, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOProfunctorOps[FR, R, E, A] = new BIO3Syntax.BIOProfunctorOps[FR, R, E, A](self)
     @inline implicit final def BIOProfunctor[FR[-_, +_, +_]: BIOFunctor3, R, E, A](self: FR[R, E, A]): BIO3Syntax.BIOFunctor3Ops[FR, R, E, A] = new BIO3Syntax.BIOFunctor3Ops[FR, R, E, A](self)
     @inline final def BIOProfunctor[FR[-_, +_, +_]: BIOProfunctor]: BIOProfunctor[FR] = implicitly
diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/BIOSyntax.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/BIOSyntax.scala
index f22aaf1fe9..d192311059 100644
--- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/BIOSyntax.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/syntax/BIOSyntax.scala
@@ -132,7 +132,17 @@ object BIOSyntax {
       F.bracket[Any, E1, A, B](r)(c => F.sync(c.close()))(use)
   }
 
-  class BIOAsyncOps[F[+_, +_], +E, +A](override protected[this] val r: F[E, A])(implicit override protected[this] val F: BIOAsync[F]) extends BIOOps(r) {
+  trait BIOParallelOps[F[+_, +_], +E, +A] {
+    protected[this] val r: F[E, A]
+    implicit protected[this] val F: BIOParallel[F]
+
+    @inline final def zipWithPar[E1 >: E, B, C](that: F[E1, B])(f: (A, B) => C): F[E1, C] = F.zipWithPar(r, that)(f)
+    @inline final def zipPar[E1 >: E, B](that: F[E1, B]): F[E1, (A, B)] = F.zipPar(r, that)
+    @inline final def zipParLeft[E1 >: E, B](that: F[E1, B]): F[E1, A] = F.zipParLeft(r, that)
+    @inline final def zipParRight[E1 >: E, B](that: F[E1, B]): F[E1, B] = F.zipParRight(r, that)
+  }
+
+  class BIOAsyncOps[F[+_, +_], +E, +A](override protected[this] val r: F[E, A])(implicit override protected[this] val F: BIOAsync[F]) extends BIOOps(r) with BIOParallelOps[F, E, A] {
     @inline final def race[E1 >: E, A1 >: A](that: F[E1, A1]): F[E1, A1] = F.race(r, that)
   }
 
@@ -162,6 +172,13 @@ object BIOSyntax {
     @inline final def BIOAsync[F[+_, +_]: BIOAsync]: BIOAsync[F] = implicitly
   }
   trait BIOImplicitPuns2 extends BIOImplicitPuns3 {
+    @inline implicit final def BIOParallel[F[+_, +_]: BIOParallel, E, A](self: F[E, A]): BIOSyntax.BIOParallelOps[F, E, A] = new BIOSyntax.BIOParallelOps[F, E, A]{
+      override val r: F[E,A] = self
+      override val F: BIOParallel[F] = BIOParallel
+    }
+    @inline final def BIOParallel[F[+_, +_]: BIOParallel]: BIOParallel[F] = implicitly
+  }
+  trait BIOImplicitPuns3 extends BIOImplicitPuns4 {
     @inline implicit final def BIO[F[+_, +_]: BIO, E, A](self: F[E, A]): BIOSyntax.BIOOps[F, E, A] = new BIOSyntax.BIOOps[F, E, A](self)
     /**
       * Shorthand for [[BIO#syncThrowable]]
@@ -173,40 +190,40 @@ object BIOSyntax {
     @inline final def BIO[F[+_, +_], A](effect: => A)(implicit F: BIO[F]): F[Throwable, A] = F.syncThrowable(effect)
     @inline final def BIO[F[+_, +_]: BIO]: BIO[F] = implicitly
   }
-  trait BIOImplicitPuns3 extends BIOImplicitPuns4 {
+  trait BIOImplicitPuns4 extends BIOImplicitPuns5 {
     @inline implicit final def BIOPanic[F[+_, +_]: BIOPanic, E, A](self: F[E, A]): BIOSyntax.BIOPanicOps[F, E, A] = new BIOSyntax.BIOPanicOps[F, E, A](self)
     @inline final def BIOPanic[F[+_, +_]: BIOPanic]: BIOPanic[F] = implicitly
   }
-  trait BIOImplicitPuns4 extends BIOImplicitPuns5 {
+  trait BIOImplicitPuns5 extends BIOImplicitPuns6 {
     @inline implicit final def BIOBracket[F[+_, +_]: BIOBracket, E, A](self: F[E, A]): BIOSyntax.BIOBracketOps[F, E, A] = new BIOSyntax.BIOBracketOps[F, E, A](self)
     @inline final def BIOBracket[F[+_, +_]: BIOBracket]: BIOBracket[F] = implicitly
   }
-  trait BIOImplicitPuns5 extends BIOImplicitPuns6 {
+  trait BIOImplicitPuns6 extends BIOImplicitPuns7 {
     @inline implicit final def BIOMonadError[F[+_, +_]: BIOMonadError, E, A](self: F[E, A]): BIOSyntax.BIOMonadErrorOps[F, E, A] = new BIOSyntax.BIOMonadErrorOps[F, E, A](self)
     @inline final def BIOMonadError[F[+_, +_]: BIOMonadError]: BIOMonadError[F] = implicitly
   }
-  trait BIOImplicitPuns6 extends BIOImplicitPuns7 {
+  trait BIOImplicitPuns7 extends BIOImplicitPuns8 {
     @inline implicit final def BIOError[F[+_, +_]: BIOError, E, A](self: F[E, A]): BIOSyntax.BIOErrorOps[F, E, A] = new BIOSyntax.BIOErrorOps[F, E, A](self)
     @inline final def BIOError[F[+_, +_]: BIOError]: BIOError[F] = implicitly
   }
-  trait BIOImplicitPuns7 extends BIOImplicitPuns8 {
+  trait BIOImplicitPuns8 extends BIOImplicitPuns9 {
     @inline implicit final def BIOGuarantee[F[+_, +_]: BIOGuarantee, E, A](self: F[E, A]): BIOSyntax.BIOGuaranteeOps[F, E, A] = new BIOSyntax.BIOGuaranteeOps[F, E, A](self)
     @inline final def BIOGuarantee[F[+_, +_]: BIOGuarantee]: BIOGuarantee[F] = implicitly
   }
-  trait BIOImplicitPuns8 extends BIOImplicitPuns9 {
+  trait BIOImplicitPuns9 extends BIOImplicitPuns10 {
     @inline implicit final def BIOMonad[F[+_, +_]: BIOMonad, E, A](self: F[E, A]): BIOSyntax.BIOMonadOps[F, E, A] = new BIOSyntax.BIOMonadOps[F, E, A](self)
     @inline final def BIOMonad[F[+_, +_]: BIOMonad]: BIOMonad[F] = implicitly
   }
-  trait BIOImplicitPuns9 extends BIOImplicitPuns10 {
+  trait BIOImplicitPuns10 extends BIOImplicitPuns11 {
     @inline implicit final def BIOApplicative[F[+_, +_]: BIOApplicative, E, A](self: F[E, A]): BIOSyntax.BIOApplicativeOps[F, E, A] = new BIOSyntax.BIOApplicativeOps[F, E, A](self)
     @inline final def BIOApplicative[F[+_, +_]: BIOApplicative]: BIOApplicative[F] = implicitly
   }
-  trait BIOImplicitPuns10 extends BIOImplicitPuns11 {
+  trait BIOImplicitPuns11 extends BIOImplicitPuns12 {
     @inline implicit final def BIOBifunctor[F[+_, +_]: BIOBifunctor, E, A](self: F[E, A]): BIOSyntax.BIOBifunctorOps[F, E, A] = new BIOSyntax.BIOBifunctorOps[F, E, A](self)
     @inline implicit final def BIOBifunctor[F[+_, +_]: BIOFunctor, E, A](self: F[E, A]): BIOSyntax.BIOFunctorOps[F, E, A] = new BIOSyntax.BIOFunctorOps[F, E, A](self)
     @inline final def BIOBifunctor[F[+_, +_]: BIOBifunctor]: BIOBifunctor[F] = implicitly
   }
-  trait BIOImplicitPuns11 {
+  trait BIOImplicitPuns12 {
     @inline implicit final def BIOFunctor[F[+_, +_]: BIOFunctor, E, A](self: F[E, A]): BIOSyntax.BIOFunctorOps[F, E, A] = new BIOSyntax.BIOFunctorOps[F, E, A](self)
     @inline final def BIOFunctor[F[+_, +_]: BIOFunctor]: BIOFunctor[F] = implicitly
   }


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 cc0ceb7832..b858a0db01 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
@@ -7,6 +7,58 @@ import scala.concurrent.duration._
 
 class BIOSyntaxTest extends AnyWordSpec {
 
+  "BIOParallel.zipPar/zipParLeft/zipParRight/zipWithPar is callable" in {
+    import izumi.functional.bio.BIOParallel
+
+    def x[F[+_, +_]: BIOParallel](a: F[Nothing, Unit], b: F[Nothing, Unit]) = {
+      a zipPar b
+      a zipParLeft b
+      a zipParRight b
+      a.zipWithPar(b)((a, b) => (a, b))
+    }
+
+    x[zio.IO](zio.UIO.succeed(()), zio.UIO.succeed(()))
+  }
+
+  "BIOParallel3.zipPar/zipParLeft/zipParRight/zipWithPar is callable" in {
+    import izumi.functional.bio.BIOParallel3
+    def x[F[-_, +_, +_]: BIOParallel3](a: F[Any, Nothing, Unit], b: F[Any, Nothing, Unit]) = {
+      a zipPar b
+      a zipParLeft b
+      a zipParRight b
+      a.zipWithPar(b)((a, b) => (a, b))
+    }
+
+    x[zio.ZIO](zio.UIO.succeed(()), zio.UIO.succeed(()))
+  }
+
+  "BIOAsync.race is callable along with all BIOParallel syntax" in {
+    import izumi.functional.bio.BIOAsync
+    def x[F[+_, +_]: BIOAsync](a: F[Nothing, Unit], b: F[Nothing, Unit]) = {
+      a zipPar b
+      a zipParLeft b
+      a zipParRight b
+      a.zipWithPar(b)((a, b) => (a, b))
+      a race b
+    }
+
+    x[zio.IO](zio.UIO.succeed(()), zio.UIO.succeed(()))
+  }
+
+  "BIOAsync3.race is callable along with all BIOParallel syntax" in {
+    import izumi.functional.bio.BIOAsync3
+
+    def x[F[-_, +_, +_]: BIOAsync3](a: F[Any, Nothing, Unit], b: F[Any, Nothing, Unit]) = {
+      a zipPar b
+      a zipParLeft b
+      a zipParRight b
+      a.zipWithPar(b)((a, b) => (a, b))
+      a race b
+    }
+
+    x[zio.ZIO](zio.UIO.succeed(()), zio.UIO.succeed(()))
+  }
+
   "BIO.apply is callable" in {
     class X[F[+_, +_]: BIO] {
       def hello = BIO(println("hello world!"))
@@ -37,14 +89,14 @@ class BIOSyntaxTest extends AnyWordSpec {
 
     def x[F[+_, +_]: BIOBracket]: F[Throwable, Int] = {
       F.pure(None).bracketCase(release = {
-        (_, _: BIOExit[Throwable, Int]) => F.unit
-      })(_ => F.pure(1))
+          (_, _: BIOExit[Throwable, Int]) => F.unit
+        })(_ => F.pure(1))
     }
     def y[F[+_, +_]: BIOBracket]: F[Throwable, Int] = {
       F.pure(None).bracketCase[Throwable, Int] {
-        case (_, BIOExit.Success(x)) => F.pure(x).as(())
-        case (_, _) => F.unit
-      } (_ => F.pure(1))
+          case (_, BIOExit.Success(x)) => F.pure(x).as(())
+          case (_, _) => F.unit
+        }(_ => F.pure(1))
     }
     def z[F[+_, +_]: BIOBracket]: F[Throwable, Int] = {
       F.pure(1).guaranteeCase {
@@ -54,9 +106,9 @@ class BIOSyntaxTest extends AnyWordSpec {
     }
     def zz[F[+_, +_]: BIOBracket]: F[Throwable, Int] = {
       F.when(F.pure(false).widenError[Throwable])(F.unit).as(1).guaranteeCase {
-        case BIOExit.Success(x) => F.pure(x).as(())
-        case _ => F.unit
-      }.widenError[Throwable]
+          case BIOExit.Success(x) => F.pure(x).as(())
+          case _ => F.unit
+        }.widenError[Throwable]
     }
 
     x[zio.IO]
@@ -107,8 +159,9 @@ class BIOSyntaxTest extends AnyWordSpec {
         }.flatMap(_.join) *>
       F.mkRef(4).flatMap(r => r.update(_ + 5) *> r.get.map(_ - 1)).fork.flatMap(_.join)
     }
-    def `attach BIOPrimitives & BIOFork3 methods to a trifunctor BIO even when not imported`[FR[-_, +_, +_]: BIOMonad3: BIOPrimitives3: BIOFork3]
-      : FR[Nothing, Nothing, Int] = {
+    def `attach BIOPrimitives & BIOFork3 methods to a trifunctor BIO even when not imported`[
+      FR[-_, +_, +_]: BIOMonad3: BIOPrimitives3: BIOFork3
+    ]: FR[Nothing, Nothing, Int] = {
       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)
     }
@@ -123,7 +176,7 @@ class BIOSyntaxTest extends AnyWordSpec {
 
   "Support BIO syntax for ZIO with wildcard import" in {
     import izumi.functional.bio._
-    zio.IO.effectTotal(List(4)).flatMap{
+    zio.IO.effectTotal(List(4)).flatMap {
       F.traverse(_)(_ => zio.IO.unit)
     } *> F.unit
     implicitly[BIOBifunctor3[zio.ZIO]]
@@ -161,70 +214,73 @@ class BIOSyntaxTest extends AnyWordSpec {
     }
     def y[FR[-_, +_, +_]: BIOLocal]: FR[Any, Throwable, Unit] = {
       F.fromKleisli {
-        F.askWith {
-          _: Int =>
-            ()
-        }.toKleisli
-      }.provide(4)
+          F.askWith {
+            _: Int =>
+              ()
+          }.toKleisli
+        }.provide(4)
     }
     def arrowAsk[FR[-_, +_, +_]: BIOArrow: BIOAsk]: FR[String, Throwable, Int] = {
       F.askWith {
-        _: Int =>
-          ()
-      }.dimap {
-        _: String =>
-          4
-      }(_ => 1)
+          _: Int =>
+            ()
+        }.dimap {
+          _: String =>
+            4
+        }(_ => 1)
     }
     def profunctorOnly[FR[-_, +_, +_]: BIOProfunctor]: FR[String, Throwable, Int] = {
-      F.contramap( ??? : FR[Unit, Throwable, Int] ) {
-        _: Int =>
-          ()
-      }.dimap {
-        _: String =>
-          4
-      }(_ => 1)
+      F.contramap(??? : FR[Unit, Throwable, Int]) {
+          _: Int =>
+            ()
+        }.dimap {
+          _: String =>
+            4
+        }(_ => 1)
         .map(_ + 2)
     }
     def bifunctorOnly[FR[-_, +_, +_]: BIOBifunctor3]: FR[Unit, Int, Int] = {
-      F.leftMap( ??? : FR[Unit, Int, Int] ) {
-        _: Int =>
-          ()
-      }.bimap({
-        _: Unit =>
-          4
-      }, _ => 1)
+      F.leftMap(??? : FR[Unit, Int, Int]) {
+          _: Int =>
+            ()
+        }.bimap(
+          {
+            _: Unit =>
+              4
+          },
+          _ => 1,
+        )
         .map(_ + 2)
     }
     def biotemporalPlusLocal[FR[-_, +_, +_]: BIOTemporal3: BIOLocal]: FR[Any, Throwable, Unit] = {
       F.fromKleisli {
-        F.askWith {
-          _: Int =>
-            ()
-        }.toKleisli
-      }.provide(4).flatMap(_ => F.unit).widenError[Throwable].leftMap(identity)
+          F.askWith {
+            _: Int =>
+              ()
+          }.toKleisli
+        }.provide(4).flatMap(_ => F.unit).widenError[Throwable].leftMap(identity)
     }
     def biomonadPlusLocal[FR[-_, +_, +_]: BIOMonad3: BIOBifunctor3: BIOLocal]: FR[Any, Throwable, Unit] = {
       F.fromKleisli {
-        F.askWith {
-          _: Int =>
-            ()
-        }.toKleisli
-      }.provide(4).flatMap(_ => F.unit).widenError[Throwable].leftMap(identity)
+          F.askWith {
+            _: Int =>
+              ()
+          }.toKleisli
+        }.provide(4).flatMap(_ => F.unit).widenError[Throwable].leftMap(identity)
     }
     def docExamples() = {
-      import izumi.functional.bio.{F, BIOMonad, BIOMonadAsk, BIOPrimitives, BIORef3}
+      import izumi.functional.bio.{BIOMonad, BIOMonadAsk, BIOPrimitives, BIORef3, F}
 
       def adder[F[+_, +_]: BIOMonad: BIOPrimitives](i: Int): F[Nothing, Int] =
         F.mkRef(0)
-         .flatMap(ref => ref.update(_ + i) *> ref.get)
+          .flatMap(ref => ref.update(_ + i) *> ref.get)
 
       // update ref from the environment and return result
       def adderEnv[F[-_, +_, +_]: BIOMonadAsk](i: Int): F[BIORef3[F, Int], Nothing, Int] =
         F.access {
           ref =>
             for {
-              _   <- ref.update(_ + i)
+              _ <- ref.update(_ + i)
               res <- ref.get
             } yield res
         }