diff --git a/core/src/main/scala/tofu/Fire.scala b/core/src/main/scala/tofu/Fire.scala deleted file mode 100644 index d0aa4cddc..000000000 --- a/core/src/main/scala/tofu/Fire.scala +++ /dev/null @@ -1,42 +0,0 @@ -package tofu - -import cats.effect.{Concurrent, Fiber} -import tofu.compat.unused -import tofu.internal.NonTofu - -import scala.util.Either -import tofu.internal.EffectComp - -trait Fire[F[_]] { - def fireAndForget[A](fa: F[A]): F[Unit] -} - -object Fire extends StartInstances[Fire] with EffectComp[Fire] - -trait Race[F[_]] extends Fire[F] { - def race[A, B](fa: F[A], fb: F[B]): F[Either[A, B]] - def never[A]: F[A] -} - -object Race extends StartInstances[Race] with EffectComp[Race] { - def never[F[_], A](implicit race: Race[F]): F[A] = race.never -} - -trait Start[F[_]] extends Fire[F] with Race[F] { - def start[A](fa: F[A]): F[Fiber[F, A]] - def racePair[A, B](fa: F[A], fb: F[B]): F[Either[(A, Fiber[F, B]), (Fiber[F, A], B)]] -} - -object Start extends StartInstances[Start] with EffectComp[Start] - -trait StartInstances[TC[f[_]] >: Start[f]] { - final implicit def concurrentInstance[F[_]](implicit F: Concurrent[F], @unused _nonTofu: NonTofu[F]): TC[F] = - new Start[F] { - def start[A](fa: F[A]): F[Fiber[F, A]] = F.start(fa) - def fireAndForget[A](fa: F[A]): F[Unit] = F.void(start(fa)) - def racePair[A, B](fa: F[A], fb: F[B]): F[Either[(A, Fiber[F, B]), (Fiber[F, A], B)]] = F.racePair(fa, fb) - def race[A, B](fa: F[A], fb: F[B]): F[Either[A, B]] = F.race(fa, fb) - def never[A]: F[A] = F.never - } - -} diff --git a/core/src/main/scala/tofu/Start.scala b/core/src/main/scala/tofu/Start.scala new file mode 100644 index 000000000..f5cc798e5 --- /dev/null +++ b/core/src/main/scala/tofu/Start.scala @@ -0,0 +1,5 @@ +package tofu + +import tofu.internal.EffectComp + +object Start extends EffectComp[Start] diff --git a/core/src/main/scala/tofu/package.scala b/core/src/main/scala/tofu/package.scala index e079c8cb5..2471e56c1 100644 --- a/core/src/main/scala/tofu/package.scala +++ b/core/src/main/scala/tofu/package.scala @@ -1,6 +1,8 @@ import cats.effect.Bracket +import cats.effect.Fiber import cats.data.Ior import tofu.kernel.KernelTypes +import cats.Id package object tofu extends KernelTypes { @@ -15,4 +17,6 @@ package object tofu extends KernelTypes { type Calculates[F[_]] = Scoped[Scoped.Calculation, F] type CalcExec[F[_]] = ScopedExecute[Scoped.Calculation, F] + + type Start[F[_]] = Fibers[F, Id, Fiber[F, *]] } diff --git a/core/src/test/scala/tofu/GuaranteeSuite.scala b/core/src/test/scala/tofu/GuaranteeSuite.scala index f2319bd5a..e4141c31c 100644 --- a/core/src/test/scala/tofu/GuaranteeSuite.scala +++ b/core/src/test/scala/tofu/GuaranteeSuite.scala @@ -2,6 +2,7 @@ package tofu import scala.annotation.nowarn import tofu.interop.CE2Kernel.CEExit +import tofu.internal.carriers.FinallyCarrier object GuaranteeSuite { diff --git a/core/src/test/scala/tofu/StartSuite.scala b/core/src/test/scala/tofu/StartSuite.scala new file mode 100644 index 000000000..79d886d83 --- /dev/null +++ b/core/src/test/scala/tofu/StartSuite.scala @@ -0,0 +1,30 @@ +package tofu + +import cats.effect.Concurrent +import scala.annotation.nowarn +import cats.effect.IO +import cats.effect.ContextShift +import tofu.syntax.start._ +import tofu.syntax.monadic._ + +@nowarn("msg=parameter") +object StartSuite { + def summonInstancesForConcurrent[F[_]: Concurrent] = { + Fire[F] + Start[F] + Race[F] + } + + def summonInstancesForIO(implicit cs: ContextShift[IO]) = { + Fire[IO] + Start[IO] + Race[IO] + } + + def testStartSyntaxCheck[A, B, F[_]: Concurrent](fa: F[A], fb: F[B]): F[(A, B)] = { + fa.racePair(fb).flatMap { + case Left((a, eb)) => eb.join.tupleLeft(a) + case Right((ea, b)) => ea.join.tupleRight(b) + } + } +} diff --git a/kernel/src/main/scala/tofu/Context.scala b/kernel/src/main/scala/tofu/Context.scala index bba13f86a..17b63254e 100644 --- a/kernel/src/main/scala/tofu/Context.scala +++ b/kernel/src/main/scala/tofu/Context.scala @@ -9,7 +9,7 @@ import cats.Monad import tofu.kernel.types._ import cats.arrow.FunctionK import tofu.syntax.monadic._ -import tofu.lift.UnliftEffect +import tofu.internal.carriers.UnliftEffect /** Declares that [[F]] can provide value of type Ctx * diff --git a/kernel/src/main/scala/tofu/Fire.scala b/kernel/src/main/scala/tofu/Fire.scala new file mode 100644 index 000000000..78aed1c04 --- /dev/null +++ b/kernel/src/main/scala/tofu/Fire.scala @@ -0,0 +1,31 @@ +package tofu + +import scala.util.Either +import tofu.internal.{EffectComp, Effect3Comp} +import tofu.internal.carriers.FibersCarrier + +trait Fire[F[_]] { + def fireAndForget[A](fa: F[A]): F[Unit] +} + +object Fire extends EffectComp[Fire] { + final implicit def byCarrier[F[_], Ex[_], Fib[_]](implicit + carrier: FibersCarrier.Aux[F, Ex, Fib] + ): Fibers[F, Ex, Fib] = carrier.content +} + +trait Race[F[_]] extends Fire[F] { + def race[A, B](fa: F[A], fb: F[B]): F[Either[A, B]] + def never[A]: F[A] +} + +object Race extends EffectComp[Race] { + def never[F[_], A](implicit race: Race[F]): F[A] = race.never +} + +trait Fibers[F[_], Exit[_], Fib[_]] extends Race[F] { + def start[A](fa: F[A]): F[Fib[A]] + def racePair[A, B](fa: F[A], fb: F[B]): F[Either[(Exit[A], Fib[B]), (Fib[A], Exit[B])]] +} + +object Fibers extends Effect3Comp[Fibers] diff --git a/kernel/src/main/scala/tofu/Guarantee.scala b/kernel/src/main/scala/tofu/Guarantee.scala index 276373fd5..ba596d9d3 100644 --- a/kernel/src/main/scala/tofu/Guarantee.scala +++ b/kernel/src/main/scala/tofu/Guarantee.scala @@ -1,8 +1,9 @@ package tofu -import tofu.internal.WBInterop import cats.MonadError import scala.annotation.unused +import tofu.internal.{EffectComp, Effect2Comp} +import tofu.internal.carriers.FinallyCarrier /** Bracket-like typeclass allowing to understand if operation was succeed * @tparam F effect process @@ -17,7 +18,7 @@ trait Guarantee[F[_]] { def bracket[A, B, C](init: F[A])(action: A => F[B])(release: (A, Boolean) => F[C]): F[B] } -object Guarantee{ +object Guarantee extends EffectComp[Guarantee] { final implicit def fromBracket[F[_], E, Exit[_]](implicit @unused ev1: MonadError[F, E], carrier: FinallyCarrier.Aux[F, E, Exit] @@ -25,7 +26,6 @@ object Guarantee{ carrier.content } - /** Bracket-like typeclass allowing to match exit of the process * @tparam F effect process * @tparam Exit structure, describing process exit like `ExitCase` or `Exit` @@ -40,19 +40,4 @@ trait Finally[F[_], Exit[_]] extends Guarantee[F] { def finallyCase[A, B, C](init: F[A])(action: A => F[B])(release: (A, Exit[B]) => F[C]): F[B] } -abstract class FinallyCarrier[F[_], E] { - type Exit[_] - val content: Finally[F, Exit] -} - -object FinallyCarrier { - type Aux[F[_], E, Ex[_]] = FinallyCarrier[F, E] { type Exit[a] = Ex[a] } - def apply[F[_], E, Ex[_]](fin: Finally[F, Ex]) = new FinallyCarrier[F, E] { - type Exit[a] = Ex[a] - val content = fin - } - - - final implicit def fromBracket[F[_], E, Exit[_]]: Aux[F, E, Exit] = - macro WBInterop.delegate1[F, E, { val `tofu.interop.CE2Kernel.finallyFromBracket`: Unit }] -} +object Finally extends Effect2Comp[Finally] diff --git a/kernel/src/main/scala/tofu/internal/DataEffectComp.scala b/kernel/src/main/scala/tofu/internal/DataEffectComp.scala index e0864ddc9..bb804ab99 100644 --- a/kernel/src/main/scala/tofu/internal/DataEffectComp.scala +++ b/kernel/src/main/scala/tofu/internal/DataEffectComp.scala @@ -8,6 +8,14 @@ trait EffectComp[TC[_[_]]] { @inline final def apply[F[_]](implicit instance: TC[F]): TC[F] = instance } +trait Effect2Comp[TC[f[_], g[_]]] { + @inline final def apply[F[_], G[_]](implicit instance: TC[F, G]): TC[F, G] = instance +} + +trait Effect3Comp[TC[f[_], g[_], h[_]]] { + @inline final def apply[F[_], G[_], H[_]](implicit instance: TC[F, G, H]): TC[F, G, H] = instance +} + trait DataComp[TC[_]] { @inline final def apply[A](implicit instance: TC[A]): TC[A] = instance } diff --git a/kernel/src/main/scala/tofu/internal/Interop.scala b/kernel/src/main/scala/tofu/internal/Interop.scala index eb42b56ae..d1535c1f1 100644 --- a/kernel/src/main/scala/tofu/internal/Interop.scala +++ b/kernel/src/main/scala/tofu/internal/Interop.scala @@ -26,5 +26,6 @@ class Interop(val c: blackbox.Context) { class WBInterop(override val c: whitebox.Context) extends Interop(c) { import c.universe._ import c.{WeakTypeTag => WTT} + def delegate0[F[_]: WTTU, N: WTT]: Tree = delegateTree[N](tc[F]) def delegate1[F[_]: WTTU, A: WTT, N: WTT]: Tree = delegateTree[N](tc[F], t[A]) } diff --git a/kernel/src/main/scala/tofu/internal/carriers/FibersCarrier.scala b/kernel/src/main/scala/tofu/internal/carriers/FibersCarrier.scala new file mode 100644 index 000000000..8a8f46f88 --- /dev/null +++ b/kernel/src/main/scala/tofu/internal/carriers/FibersCarrier.scala @@ -0,0 +1,21 @@ +package tofu.internal.carriers +import tofu.Fibers +import tofu.internal.WBInterop + +abstract class FibersCarrier[F[_]] { + type Fib[_] + type Exit[_] + val content: Fibers[F, Exit, Fib] +} + +object FibersCarrier { + type Aux[F[_], Ex[_], Fb[_]] = FibersCarrier[F] { type Exit[a] = Ex[a]; type Fib[a] = Fb[a]; } + def apply[F[_], Ex[_], Fb[_]](fin: Fibers[F, Ex, Fb]) = new FibersCarrier[F] { + type Fib[a] = Fb[a] + type Exit[a] = Ex[a] + val content = fin + } + + final implicit def startFromConcurrent[F[_], Exit[_], Fiber[_]]: Aux[F, Exit, Fiber] = + macro WBInterop.delegate0[F, { val `tofu.interop.CE2Kernel.startFromConcurrent`: Unit }] +} diff --git a/kernel/src/main/scala/tofu/internal/carriers/FinallyCarrier.scala b/kernel/src/main/scala/tofu/internal/carriers/FinallyCarrier.scala new file mode 100644 index 000000000..1cd34df7c --- /dev/null +++ b/kernel/src/main/scala/tofu/internal/carriers/FinallyCarrier.scala @@ -0,0 +1,20 @@ +package tofu.internal.carriers + +import tofu.Finally +import tofu.internal.WBInterop + +abstract class FinallyCarrier[F[_], E] { + type Exit[_] + val content: Finally[F, Exit] +} + +object FinallyCarrier { + type Aux[F[_], E, Ex[_]] = FinallyCarrier[F, E] { type Exit[a] = Ex[a] } + def apply[F[_], E, Ex[_]](fin: Finally[F, Ex]) = new FinallyCarrier[F, E] { + type Exit[a] = Ex[a] + val content = fin + } + + final implicit def fromBracket[F[_], E, Exit[_]]: Aux[F, E, Exit] = + macro WBInterop.delegate1[F, E, { val `tofu.interop.CE2Kernel.finallyFromBracket`: Unit }] +} diff --git a/kernel/src/main/scala/tofu/internal/carriers/UnliftEffect.scala b/kernel/src/main/scala/tofu/internal/carriers/UnliftEffect.scala new file mode 100644 index 000000000..832513535 --- /dev/null +++ b/kernel/src/main/scala/tofu/internal/carriers/UnliftEffect.scala @@ -0,0 +1,14 @@ +package tofu.internal.carriers + +import tofu.internal.Interop +import tofu.lift.Unlift + +// This is purely workaround for scala 2.12 +// Which denies to unfold a macro (and recieve a type error) +// before checking an implicit for eligibility +class UnliftEffect[F[_], G[_]](val value: Unlift[F, G]) extends AnyVal + +object UnliftEffect { + final implicit def unliftIOEffect[F[_], G[_]]: UnliftEffect[F, G] = + macro Interop.delegate[UnliftEffect[F, G], G, { val `tofu.interop.CE2Kernel.unliftEffect`: Unit }] +} diff --git a/kernel/src/main/scala/tofu/lift/Unlift.scala b/kernel/src/main/scala/tofu/lift/Unlift.scala index f8ef80ee2..f0283e06c 100644 --- a/kernel/src/main/scala/tofu/lift/Unlift.scala +++ b/kernel/src/main/scala/tofu/lift/Unlift.scala @@ -7,7 +7,6 @@ import cats.{Applicative, FlatMap, Functor, Monad, ~>} import syntax.funk._ import tofu.optics.Contains import tofu.syntax.monadic._ -import tofu.internal.Interop import kernel.types._ trait Lift[F[_], G[_]] { @@ -103,12 +102,4 @@ object Unlift { } } -// This is purely workaround for scala 2.12 -// Which denies to unfold a macro (and recieve a type error) -// before checking an implicit for eligibility -class UnliftEffect[F[_], G[_]](val value: Unlift[F, G]) extends AnyVal -object UnliftEffect { - final implicit def unliftIOEffect[F[_], G[_]]: UnliftEffect[F, G] = - macro Interop.delegate[UnliftEffect[F, G], G, { val `tofu.interop.CE2Kernel.unliftEffect`: Unit }] -} diff --git a/core/src/main/scala/tofu/syntax/fire.scala b/kernel/src/main/scala/tofu/syntax/fire.scala similarity index 59% rename from core/src/main/scala/tofu/syntax/fire.scala rename to kernel/src/main/scala/tofu/syntax/fire.scala index 1c20e667f..591b05d5b 100644 --- a/core/src/main/scala/tofu/syntax/fire.scala +++ b/kernel/src/main/scala/tofu/syntax/fire.scala @@ -1,7 +1,6 @@ package tofu.syntax -import tofu.{Fire, Race, Start} -import cats.effect.Fiber +import tofu.{Fire, Race, Fibers} object fire { final implicit class FireOps[F[_], A](private val fa: F[A]) extends AnyVal { @@ -15,8 +14,10 @@ object race { } object start { final implicit class StartOps[F[_], A](private val fa: F[A]) extends AnyVal { - def start(implicit F: Start[F]): F[Fiber[F, A]] = F.start(fa) - def racePair[F1[x] >: F[x], B](fb: F1[B])(implicit F: Start[F1]): F1[Either[(A, Fiber[F1, B]), (Fiber[F1, A], B)]] = - F.racePair(fa, fb) + def start[Exit[_], Fib[_]](implicit F: Fibers[F, Exit, Fib]): F[Fib[A]] = F.start(fa) + def racePair[F1[x] >: F[x], B, Exit[_], Fib[_]](fb: F1[B])(implicit + F: Fibers[F1, Exit, Fib] + ): F1[Either[(Exit[A], Fib[B]), (Fib[A], Exit[B])]] = + F.racePair[A, B](fa, fb) } } diff --git a/kernelCE2Interop/src/main/scala/tofu/interop/CE2Kernel.scala b/kernelCE2Interop/src/main/scala/tofu/interop/CE2Kernel.scala index 15ca12169..8086d4625 100644 --- a/kernelCE2Interop/src/main/scala/tofu/interop/CE2Kernel.scala +++ b/kernelCE2Interop/src/main/scala/tofu/interop/CE2Kernel.scala @@ -1,4 +1,5 @@ -package tofu.interop +package tofu +package interop import tofu.lift.Unlift import cats.effect.Sync @@ -6,18 +7,16 @@ import tofu.Delay import cats.effect.Effect import cats.effect.IO import tofu.syntax.monadic._ -import cats.~> -import tofu.lift.UnliftEffect +import cats.{~>, Id} import cats.effect.Concurrent import cats.effect.Timer +import cats.effect.Fiber import scala.concurrent.duration.FiniteDuration -import tofu.Timeout import tofu.internal.NonTofu import tofu.compat.unused import cats.effect.Bracket -import tofu.Finally import cats.effect.ExitCase -import tofu.FinallyCarrier +import tofu.internal.carriers._ object CE2Kernel { // 2.12 sometimes gets mad on Const partial alias during implicit search @@ -50,11 +49,25 @@ object CE2Kernel { F.bracketCase(init)(action) { case (a, exit) => F.void(release(a, exit)) } - def bracket[A, B, C](init: F[A])(action: A => F[B])(release: (A, Boolean) => F[C]): F[B] = + def bracket[A, B, C](init: F[A])(action: A => F[B])(release: (A, Boolean) => F[C]): F[B] = F.bracketCase(init)(action) { case (a, ExitCase.Completed) => F.void(release(a, true)) case (a, _) => F.void(release(a, false)) } } ) + + final implicit def startFromConcurrent[F[_]](implicit + F: Concurrent[F], + @unused _nonTofu: NonTofu[F] + ): FibersCarrier.Aux[F, Id, Fiber[F, *]] = + FibersCarrier[F, Id, Fiber[F, *]]( + new Fibers[F, Id, Fiber[F, *]] { + def start[A](fa: F[A]): F[Fiber[F, A]] = F.start(fa) + def fireAndForget[A](fa: F[A]): F[Unit] = F.void(start(fa)) + def racePair[A, B](fa: F[A], fb: F[B]): F[Either[(A, Fiber[F, B]), (Fiber[F, A], B)]] = F.racePair(fa, fb) + def race[A, B](fa: F[A], fb: F[B]): F[Either[A, B]] = F.race(fa, fb) + def never[A]: F[A] = F.never + } + ) }