izumi.functional.quasi.QuasiIO.scala Maven / Gradle / Ivy
The newest version!
package izumi.functional.quasi
import cats.effect.kernel.Outcome
import izumi.functional.quasi.QuasiIO.QuasiIOIdentity
import izumi.functional.bio.data.Morphism1
import izumi.functional.bio.{Applicative2, Exit, Functor2, IO2, TypedError}
import izumi.fundamentals.orphans.{`cats.Applicative`, `cats.Functor`, `cats.effect.kernel.Sync`}
import izumi.fundamentals.platform.functional.Identity
import java.util.concurrent.atomic.AtomicReference
import scala.annotation.tailrec
import scala.language.implicitConversions
import scala.util.{Failure, Success, Try}
/**
* Evidence that `F` is _almost_ like an `IO` monad, but not quite –
* because we also allow an impure [[izumi.fundamentals.platform.functional.Identity]] instance,
* for which `maybeSuspend` does not actually suspend!
*
* If you use this interface and forget to add manual suspensions with by-name parameters everywhere,
* you're going to get weird behavior for Identity instance.
*
* This interface serves internal needs of `distage` for interoperability with all existing
* Scala effect types and also with `Identity`, you should NOT refer to it in your code if possible.
*
* This type is public because you may want to define your own instances, if a suitable instance of [[izumi.distage.modules.DefaultModule]]
* is missing for your custom effect type.
* For application logic, prefer writing against typeclasses in [[izumi.functional.bio]] or [[cats]] instead.
*
* @see [[izumi.distage.modules.DefaultModule]] - `DefaultModule` makes instances of `QuasiIO` for cats-effect, ZIO,
* monix, monix-bio, `Identity` and others available for summoning in your wiring automatically
*
* TODO: we want to get rid of this by providing Identity implementations for relevant BIO typeclasses
* See https://github.com/7mind/izumi/issues/787
*/
trait QuasiIO[F[_]] extends QuasiPrimitives[F] {
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
def guaranteeOnFailure[A](fa: => F[A])(cleanupOnFailure: Throwable => F[Unit]): F[A] = {
bracketCase(acquire = unit)(release = (_, maybeExit) => maybeExit.fold(unit)(cleanupOnFailure))(use = _ => fa)
}
def bracketCase[A, B](acquire: => F[A])(release: (A, Option[Throwable]) => F[Unit])(use: A => F[B]): F[B]
final def bracketAuto[A <: AutoCloseable, B](acquire: => F[A])(use: A => F[B]): F[B] = bracket(acquire)(a => maybeSuspend(a.close()))(use)
/** A weaker version of `delay`. Does not guarantee _actual_
* suspension of side-effects, because QuasiIO[Identity] is allowed
*/
def maybeSuspend[A](eff: => A): F[A]
def maybeSuspendEither[A](eff: => Either[Throwable, A]): F[A]
/**
* A stronger version of `handleErrorWith` (cats-effect) or `catchAll` (ZIO),
* the difference is that this will _also_ intercept Throwable defects in `ZIO`,
* not only typed errors.
*
* @note This function is meant for _RECOVERING_ errors, for _REPORTING_ errors use [[definitelyRecoverWithTrace]]
* and include the full trace of the error in the report.
*/
def definitelyRecoverUnsafeIgnoreTrace[A](action: => F[A])(recover: Throwable => F[A]): F[A]
/**
* Like [[definitelyRecoverUnsafeIgnoreTrace]], but the second parameter to the callback
* contains the effect's debugging information, possibly convertable to a Throwable via [[Exit.Trace#toThrowable]],
* or that could be used to mutably enhance the left-hand-side Throwable value via [[Exit.Trace#unsafeAttachTrace]]
*
* @note [[Exit.Trace#unsafeAttachTrace]] may perform side-effects to the original Throwable argument on the left,
* the left throwable should be DISCARDED after calling the callback.
* (e.g. in case of `ZIO`, the callback will mutate the throwable and attach a ZIO Trace to it.)
*/
def definitelyRecoverWithTrace[A](action: => F[A])(recoverWithTrace: (Throwable, Exit.Trace[Throwable]) => F[A]): F[A]
def redeem[A, B](action: => F[A])(failure: Throwable => F[B], success: A => F[B]): F[B]
def fail[A](t: => Throwable): F[A]
override def suspendF[A](effAction: => F[A]): F[A] = {
flatMap(maybeSuspend(effAction))(identity)
}
override def mkRef[A](a: A): F[QuasiRef[F, A]] = {
QuasiRef.fromMaybeSuspend(a)(Morphism1(f => maybeSuspend(f())))
}
}
object QuasiIO extends LowPriorityQuasiIOInstances {
@inline def apply[F[_]: QuasiIO]: QuasiIO[F] = implicitly
object syntax {
implicit def suspendedSyntax[F[_], A](fa: => F[A]): QuasiIOSuspendedSyntax[F, A] = new QuasiIOSuspendedSyntax(() => fa)
implicit final class QuasiIOSyntax[F[_], A](private val fa: F[A]) extends AnyVal {
@inline def map[B](f: A => B)(implicit F: QuasiFunctor[F]): F[B] = F.map(fa)(f)
@inline def flatMap[B](f: A => F[B])(implicit F: QuasiPrimitives[F]): F[B] = F.flatMap(fa)(f)
}
final class QuasiIOSuspendedSyntax[F[_], A](private val fa: () => F[A]) extends AnyVal {
@inline def guarantee(`finally`: => F[Unit])(implicit F: QuasiPrimitives[F]): F[A] = {
F.guarantee(fa())(`finally`)
}
@inline def guaranteeOnFailure(cleanupOnFailure: Throwable => F[Unit])(implicit F: QuasiIO[F]): F[A] = {
F.guaranteeOnFailure(fa())(cleanupOnFailure)
}
}
}
@inline implicit def quasiIOIdentity: QuasiIO[Identity] = QuasiIOIdentity
private[quasi] object QuasiIOIdentity extends QuasiIO[Identity] {
override def pure[A](a: A): Identity[A] = a
override def map[A, B](fa: Identity[A])(f: A => B): Identity[B] = f(fa)
override def map2[A, B, C](fa: Identity[A], fb: => Identity[B])(f: (A, B) => C): Identity[C] = f(fa, fb)
override def flatMap[A, B](a: A)(f: A => Identity[B]): Identity[B] = f(a)
@tailrec override def tailRecM[A, B](a: A)(f: A => Identity[Either[A, B]]): Identity[B] = {
f(a) match {
case Left(next) => tailRecM(next)(f)
case Right(res) => res
}
}
override def maybeSuspend[A](eff: => A): Identity[A] = eff
override def maybeSuspendEither[A](eff: => Either[Throwable, A]): Identity[A] = eff match {
case Left(err) => throw err
case Right(v) => v
}
override def suspendF[A](effAction: => A): Identity[A] = effAction
override def definitelyRecoverUnsafeIgnoreTrace[A](fa: => Identity[A])(recover: Throwable => Identity[A]): Identity[A] = {
try { fa }
catch { case t: Throwable => recover(t) }
}
override def definitelyRecoverWithTrace[A](action: => Identity[A])(recoverCause: (Throwable, Exit.Trace[Throwable]) => Identity[A]): Identity[A] = {
definitelyRecoverUnsafeIgnoreTrace(action)(e => recoverCause(e, Exit.Trace.ThrowableTrace(e)))
}
override def redeem[A, B](action: => Identity[A])(failure: Throwable => Identity[B], success: A => Identity[B]): Identity[B] = {
TryNonFatal(action) match {
case Failure(exception) =>
failure(exception)
case Success(value) =>
success(value)
}
}
override def bracket[A, B](acquire: => Identity[A])(release: A => Identity[Unit])(use: A => Identity[B]): Identity[B] = {
val a = acquire
try use(a)
finally release(a)
}
override def bracketCase[A, B](acquire: => Identity[A])(release: (A, Option[Throwable]) => Identity[Unit])(use: A => Identity[B]): Identity[B] = {
val a = acquire
TryWithFatal(use(a)) match {
case Failure(exception) =>
release(a, Some(exception))
throw exception
case Success(value) =>
release(a, None)
value
}
}
override def guarantee[A](fa: => Identity[A])(`finally`: => Identity[Unit]): Identity[A] = {
try { fa }
finally `finally`
}
override def guaranteeOnFailure[A](fa: => Identity[A])(cleanupOnFailure: Throwable => Identity[Unit]): Identity[A] = {
try { fa }
catch { case t: Throwable => cleanupOnFailure(t); throw t }
}
override def fail[A](t: => Throwable): Identity[A] = throw t
override def traverse[A, B](l: Iterable[A])(f: A => Identity[B]): Identity[List[B]] = l.iterator.map(f).toList
override def traverse_[A](l: Iterable[A])(f: A => Identity[Unit]): Identity[Unit] = l.foreach(f)
@inline private def TryWithFatal[A](r: => A): Try[A] = {
try Success(r)
catch { case t: Throwable => Failure(t) }
}
@inline private def TryNonFatal[A](r: => A): Try[A] = Try(r)
}
}
private[quasi] sealed trait LowPriorityQuasiIOInstances extends LowPriorityQuasiIOInstances1 {
implicit def fromBIO[F[+_, +_]](implicit F: IO2[F]): QuasiIO[F[Throwable, _]] = {
type E = Throwable
new QuasiPrimitivesFromBIO[F, Throwable] with QuasiIO[F[Throwable, _]] {
override final def suspendF[A](effAction: => F[E, A]): F[E, A] = super[QuasiPrimitivesFromBIO].suspendF(effAction)
override final def mkRef[A](a: A): F[E, QuasiRef[F[E, _], A]] = super[QuasiPrimitivesFromBIO].mkRef(a)
override def maybeSuspend[A](eff: => A): F[E, A] = F.syncThrowable(eff)
override def maybeSuspendEither[A](eff: => Either[E, A]): F[E, A] = F.fromEither(eff)
override def definitelyRecoverUnsafeIgnoreTrace[A](action: => F[E, A])(recover: E => F[E, A]): F[E, A] = {
F.suspend(action).sandbox.catchAll(recover apply _.toThrowable)
}
override def definitelyRecoverWithTrace[A](action: => F[E, A])(recover: (E, Exit.Trace[E]) => F[E, A]): F[E, A] = {
F.suspend(action).sandbox.catchAll(e => recover(e.toThrowable, e.trace))
}
override def redeem[A, B](action: => F[E, A])(failure: E => F[E, B], success: A => F[E, B]): F[E, B] = {
action.redeem(failure, success)
}
override def fail[A](t: => E): F[E, A] = F.fail(t)
override def bracketCase[A, B](acquire: => F[E, A])(release: (A, Option[E]) => F[E, Unit])(use: A => F[E, B]): F[E, B] = {
F.bracketCase[E, A, B](acquire = F.suspend(acquire))(release = {
case (a, exit) =>
exit match {
case Exit.Success(_) => release(a, None).orTerminate
case failure: Exit.Failure[E] => release(a, Some(failure.toThrowable)).orTerminate
}
})(use = use)
}
override def guaranteeOnFailure[A](fa: => F[E, A])(cleanupOnFailure: E => F[E, Unit]): F[E, A] = {
F.guaranteeOnFailure(F.suspend(fa), (e: Exit.Failure[E]) => cleanupOnFailure(e.toThrowable).orTerminate)
}
}
}
}
private[quasi] sealed trait LowPriorityQuasiIOInstances1 {
/**
* This instance uses 'no more orphans' trick to provide an Optional instance
* only IFF you have cats-effect as a dependency without REQUIRING a cats-effect dependency.
*
* Optional instance via https://blog.7mind.io/no-more-orphans.html
*/
implicit def fromCats[F[_], Sync[_[_]]: `cats.effect.kernel.Sync`](implicit F0: Sync[F]): QuasiIO[F] = {
val F = F0.asInstanceOf[cats.effect.kernel.Sync[F]]
new QuasiPrimitivesFromCats[F](F) with QuasiIO[F] {
override final def suspendF[A](effAction: => F[A]): F[A] = super[QuasiPrimitivesFromCats].suspendF(effAction)
override final def mkRef[A](a: A): F[QuasiRef[F, A]] = super[QuasiPrimitivesFromCats].mkRef(a)
override def maybeSuspend[A](eff: => A): F[A] = F.delay(eff)
override def maybeSuspendEither[A](eff: => Either[Throwable, A]): F[A] = F.defer(F.fromEither(eff))
override def definitelyRecoverUnsafeIgnoreTrace[A](action: => F[A])(recover: Throwable => F[A]): F[A] = {
F.handleErrorWith(F.defer(action))(recover)
}
override def definitelyRecoverWithTrace[A](action: => F[A])(recoverCause: (Throwable, Exit.Trace[Throwable]) => F[A]): F[A] = {
definitelyRecoverUnsafeIgnoreTrace(action)(e => recoverCause(e, Exit.Trace.ThrowableTrace(e)))
}
override def redeem[A, B](action: => F[A])(failure: Throwable => F[B], success: A => F[B]): F[B] = {
F.redeemWith(action)(failure, success)
}
override def fail[A](t: => Throwable): F[A] = F.defer(F.raiseError(t))
override def bracketCase[A, B](acquire: => F[A])(release: (A, Option[Throwable]) => F[Unit])(use: A => F[B]): F[B] = {
F.bracketCase(acquire = F.defer(acquire))(use = use)(release = {
case (a, exitCase) =>
exitCase match {
case Outcome.Succeeded(_) => release(a, None)
case Outcome.Errored(e) => release(a, Some(e))
case Outcome.Canceled() => release(a, Some(new InterruptedException("cats.effect.kernel.Outcome.Canceled()")))
}
})
}
override def guaranteeOnFailure[A](fa: => F[A])(cleanupOnFailure: Throwable => F[Unit]): F[A] = {
F.guaranteeCase(F.defer(fa)) {
case Outcome.Succeeded(_) => F.unit
case Outcome.Errored(e) => cleanupOnFailure(e)
case Outcome.Canceled() => cleanupOnFailure(new InterruptedException("cats.effect.kernel.Outcome.Canceled()"))
}
}
}
}
}
/**
* Evidence that `F` supports a subset of [[QuasiIO]] capabilities - state, non-effectful not-guaranteed suspension,
* and setting finalizers that can't inspect the error.
*
* Internal use class, as with [[QuasiIO]], it's only public so that you can define your own instances,
* better use [[izumi.functional.bio]] or [[cats]] typeclasses for application logic.
*/
trait QuasiPrimitives[F[_]] extends QuasiApplicative[F] {
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] = {
flatMap(f(a)) {
case Left(next) => tailRecM(next)(f)
case Right(res) => pure(res)
}
}
def bracket[A, B](acquire: => F[A])(release: A => F[Unit])(use: A => F[B]): F[B]
def guarantee[A](fa: => F[A])(`finally`: => F[Unit]): F[A] = bracket(acquire = unit)(release = _ => `finally`)(use = _ => fa)
def mkRef[A](a: A): F[QuasiRef[F, A]]
def suspendF[A](effAction: => F[A]): F[A]
def traverse[A, B](l: Iterable[A])(f: A => F[B]): F[List[B]] = {
// All reasonable effect types will be stack-safe (not heap-safe!) on left-associative flatMaps so foldLeft is ok here.
// note: overriden in all default impls
l.foldLeft(pure(List.empty[B])) {
(acc, a) =>
flatMap(acc)(list => map(f(a))(r => list ++ List(r)))
}
}
def traverse_[A](l: Iterable[A])(f: A => F[Unit]): F[Unit] = {
// All reasonable effect types will be stack-safe (not heap-safe!) on left-associative flatMaps so foldLeft is ok here.
// note: overriden in all default impls
l.foldLeft(unit) {
(acc, a) =>
flatMap(acc)(_ => f(a))
}
}
}
object QuasiPrimitives extends LowPriorityQuasiPrimitivesInstances {
@inline def apply[F[_]: QuasiPrimitives]: QuasiPrimitives[F] = implicitly
@inline implicit def quasiPrimitivesIdentity: QuasiPrimitives[Identity] = QuasiIOIdentity
}
private[quasi] sealed trait LowPriorityQuasiPrimitivesInstances extends LowPriorityQuasiPrimitivesInstances1 {
implicit def fromBIO[F[+_, +_], E](implicit F: IO2[F]): QuasiPrimitives[F[E, _]] = new QuasiPrimitivesFromBIO[F, E]
}
private[quasi] sealed trait LowPriorityQuasiPrimitivesInstances1 {
/**
* This instance uses 'no more orphans' trick to provide an Optional instance
* only IFF you have cats-effect as a dependency without REQUIRING a cats-effect dependency.
*
* Optional instance via https://blog.7mind.io/no-more-orphans.html
*/
implicit def fromCats[F[_], Sync[_[_]]: `cats.effect.kernel.Sync`](implicit F0: Sync[F]): QuasiPrimitives[F] = {
new QuasiPrimitivesFromCats(F0.asInstanceOf[cats.effect.kernel.Sync[F]])
}
}
private[quasi] sealed class QuasiPrimitivesFromBIO[F[+_, +_], E](implicit F: IO2[F]) extends QuasiPrimitives[F[E, _]] {
override def suspendF[A](f: => F[E, A]): F[E, A] = F.sync(f).flatten
override final def pure[A](a: A): F[E, A] = F.pure(a)
override final def map[A, B](fa: F[E, A])(f: A => B): F[E, B] = F.map(fa)(f)
override final def map2[A, B, C](fa: F[E, A], fb: => F[E, B])(f: (A, B) => C): F[E, C] = F.map2(fa, fb)(f)
override final def flatMap[A, B](fa: F[E, A])(f: A => F[E, B]): F[E, B] = F.flatMap(fa)(f)
override final def tailRecM[A, B](a: A)(f: A => F[E, Either[A, B]]): F[E, B] = F.tailRecM(a)(f)
override final def bracket[A, B](acquire: => F[E, A])(release: A => F[E, Unit])(use: A => F[E, B]): F[E, B] = {
F.bracket(acquire = suspendF(acquire))(release = release(_).catchAll {
case e: Throwable => F.terminate(e)
case e => F.terminate(TypedError(e))
})(use = use)
}
override final def guarantee[A](fa: => F[E, A])(`finally`: => F[E, Unit]): F[E, A] = {
F.guarantee(
suspendF(fa),
suspendF(`finally`).catchAll {
case e: Throwable => F.terminate(e)
case e => F.terminate(TypedError(e))
},
)
}
override def mkRef[A](a: A): F[E, QuasiRef[F[E, _], A]] = {
QuasiRef.fromMaybeSuspend(a)(Morphism1(f => F.sync(f())))
}
override final def traverse[A, B](l: Iterable[A])(f: A => F[E, B]): F[E, List[B]] = F.traverse(l)(f)
override final def traverse_[A](l: Iterable[A])(f: A => F[E, Unit]): F[E, Unit] = F.traverse_(l)(f)
}
private[quasi] sealed class QuasiPrimitivesFromCats[F[_]](F: cats.effect.kernel.Sync[F]) extends QuasiPrimitives[F] {
override def suspendF[A](effAction: => F[A]): F[A] = F.defer(effAction)
override final def pure[A](a: A): F[A] = F.pure(a)
override final def map[A, B](fa: F[A])(f: A => B): F[B] = F.map(fa)(f)
override final def map2[A, B, C](fa: F[A], fb: => F[B])(f: (A, B) => C): F[C] = F.flatMap(fa)(a => F.map(fb)(f(a, _)))
override final def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] = F.flatMap(fa)(f)
override final def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] = F.tailRecM(a)(f)
override final def bracket[A, B](acquire: => F[A])(release: A => F[Unit])(use: A => F[B]): F[B] = {
F.bracket(acquire = F.defer(acquire))(use = use)(release = release)
}
override final def guarantee[A](fa: => F[A])(`finally`: => F[Unit]): F[A] = {
F.guarantee(F.defer(fa), F.defer(`finally`))
}
override def mkRef[A](a: A): F[QuasiRef[F, A]] = {
QuasiRef.fromMaybeSuspend(a)(Morphism1(f => F.delay(f())))
}
override final def traverse[A, B](l: Iterable[A])(f: A => F[B]): F[List[B]] = cats.instances.list.catsStdInstancesForList.traverse(l.toList)(f)(F)
override final def traverse_[A](l: Iterable[A])(f: A => F[Unit]): F[Unit] = cats.instances.list.catsStdInstancesForList.traverse_(l.toList)(f)(F)
}
/**
* An `Applicative` capability for `F`. Unlike `QuasiIO` there's nothing "quasi" about it – it makes sense. But named like that for consistency anyway.
*
* Internal use class, as with [[QuasiIO]], it's only public so that you can define your own instances,
* better use [[izumi.functional.bio]] or [[cats]] typeclasses for application logic.
*/
trait QuasiApplicative[F[_]] extends QuasiFunctor[F] {
def pure[A](a: A): F[A]
def map2[A, B, C](fa: F[A], fb: => F[B])(f: (A, B) => C): F[C]
def traverse[A, B](l: Iterable[A])(f: A => F[B]): F[List[B]]
def traverse_[A](l: Iterable[A])(f: A => F[Unit]): F[Unit]
final val unit: F[Unit] = pure(())
final def when(cond: Boolean)(ifTrue: => F[Unit]): F[Unit] = if (cond) ifTrue else unit
final def unless(cond: Boolean)(ifFalse: => F[Unit]): F[Unit] = if (cond) unit else ifFalse
final def ifThenElse[A](cond: Boolean)(ifTrue: => F[A], ifFalse: => F[A]): F[A] = if (cond) ifTrue else ifFalse
}
object QuasiApplicative extends LowPriorityQuasiApplicativeInstances {
@inline def apply[F[_]: QuasiApplicative]: QuasiApplicative[F] = implicitly
@inline implicit def quasiApplicativeIdentity: QuasiApplicative[Identity] = QuasiIOIdentity
}
private[quasi] sealed trait LowPriorityQuasiApplicativeInstances extends LowPriorityQuasiApplicativeInstances1 {
implicit def fromBIO[F[+_, +_], E](implicit F: Applicative2[F]): QuasiApplicative[F[E, _]] = {
new QuasiApplicative[F[E, _]] {
override def pure[A](a: A): F[E, A] = F.pure(a)
override def map[A, B](fa: F[E, A])(f: A => B): F[E, B] = F.map(fa)(f)
override def map2[A, B, C](fa: F[E, A], fb: => F[E, B])(f: (A, B) => C): F[E, C] = F.map2(fa, fb)(f)
override def traverse[A, B](l: Iterable[A])(f: A => F[E, B]): F[E, List[B]] = F.traverse(l)(f)
override def traverse_[A](l: Iterable[A])(f: A => F[E, Unit]): F[E, Unit] = F.traverse_(l)(f)
}
}
}
private[quasi] sealed trait LowPriorityQuasiApplicativeInstances1 {
/**
* This instance uses 'no more orphans' trick to provide an Optional instance
* only IFF you have cats-core as a dependency without REQUIRING a cats-core dependency.
*
* Optional instance via https://blog.7mind.io/no-more-orphans.html
*/
implicit def fromCats[F[_], Applicative[_[_]]: `cats.Applicative`](implicit F0: Applicative[F]): QuasiApplicative[F] = {
val F = F0.asInstanceOf[cats.Applicative[F]]
new QuasiApplicative[F] {
override def pure[A](a: A): F[A] = F.pure(a)
override def map[A, B](fa: F[A])(f: A => B): F[B] = F.map(fa)(f)
override def map2[A, B, C](fa: F[A], fb: => F[B])(f: (A, B) => C): F[C] = F.map2(fa, fb)(f)
override def traverse[A, B](l: Iterable[A])(f: A => F[B]): F[List[B]] = cats.instances.list.catsStdInstancesForList.traverse(l.toList)(f)(F)
override def traverse_[A](l: Iterable[A])(f: A => F[Unit]): F[Unit] = cats.instances.list.catsStdInstancesForList.traverse_(l.toList)(f)(F)
}
}
}
/**
* A `Functor` capability for `F`. Unlike `QuasiIO` there's nothing "quasi" about it – it makes sense. But named like that for consistency anyway.
*
* Internal use class, as with [[QuasiIO]], it's only public so that you can define your own instances,
* better use [[izumi.functional.bio]] or [[cats]] typeclasses for application logic.
*/
trait QuasiFunctor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
final def widen[A, B >: A](fa: F[A]): F[B] = fa.asInstanceOf[F[B]]
}
object QuasiFunctor extends LowPriorityQuasiFunctorInstances {
@inline def apply[F[_]: QuasiFunctor]: QuasiFunctor[F] = implicitly
@inline implicit def quasiFunctorIdentity: QuasiFunctor[Identity] = {
// FIXME: This instance's type is QuasiFunctor not QuasiApplicative to Scala 3 bug https://github.com/lampepfl/dotty/issues/16431
QuasiIOIdentity
}
}
private[quasi] sealed trait LowPriorityQuasiFunctorInstances extends LowPriorityQuasiFunctorInstances1 {
implicit def fromBIO[F[+_, +_], E](implicit F: Functor2[F]): QuasiFunctor[F[E, _]] = {
new QuasiFunctor[F[E, _]] {
override def map[A, B](fa: F[E, A])(f: A => B): F[E, B] = F.map(fa)(f)
}
}
}
private[quasi] sealed trait LowPriorityQuasiFunctorInstances1 {
/**
* This instance uses 'no more orphans' trick to provide an Optional instance
* only IFF you have cats-core as a dependency without REQUIRING a cats-core dependency.
*
* Optional instance via https://blog.7mind.io/no-more-orphans.html
*/
implicit def fromCats[F[_], Functor[_[_]]: `cats.Functor`](implicit F0: Functor[F]): QuasiFunctor[F] = {
val F = F0.asInstanceOf[cats.Functor[F]]
new QuasiFunctor[F] {
override def map[A, B](fa: F[A])(f: A => B): F[B] = F.map(fa)(f)
}
}
}
trait QuasiRef[F[_], A] {
def get: F[A]
def set(a: A): F[Unit]
def update(f: A => A): F[Unit]
}
object QuasiRef {
def mk[F[_], A](a: A)(implicit F: QuasiPrimitives[F]): F[QuasiRef[F, A]] = F.mkRef(a)
def fromMaybeSuspend[F[_], A](a: A)(maybeSuspend: Morphism1[() => _, F]): F[QuasiRef[F, A]] = {
maybeSuspend {
() =>
val ref = new AtomicReference[A](a)
new QuasiRef[F, A] {
override def get: F[A] = maybeSuspend(() => ref.get())
override def set(a: A): F[Unit] = maybeSuspend(() => ref.set(a))
override def update(f: A => A): F[Unit] = {
maybeSuspend {
() =>
var oldValue = ref.get()
while (!ref.compareAndSet(oldValue, f(oldValue))) {
oldValue = ref.get()
}
}
}
}
}
}
}