All Downloads are FREE. Search and download functionalities are using the official Maven repository.

izumi.functional.bio.syntax.Syntax2.scala Maven / Gradle / Ivy

The newest version!
package izumi.functional.bio.syntax

import izumi.functional.bio.*
import izumi.fundamentals.platform.language.SourceFilePositionMaterializer

import scala.annotation.unused
import scala.concurrent.duration.{Duration, FiniteDuration}
import scala.language.implicitConversions

/**
  * All implicit syntax in BIO is available automatically without wildcard imports
  * with the help of so-called "implicit punning", as in the following example:
  *
  * {{{
  *   import izumi.functional.bio.Monad2
  *
  *   def loop[F[+_, +_]: Monad2]: F[Nothing, Nothing] = {
  *     val unitEffect: F[Nothing, Unit] = Monad2[F].unit
  *     unitEffect.flatMap(loop)
  *   }
  * }}}
  *
  * Note that a `.flatMap` method is available on the `unitEffect` value of an abstract type parameter `F`,
  * even though we did not import any syntax implicits using a wildcard import.
  *
  * The `flatMap` method was added by the implicit punning on the `Monad2` name.
  * In short, implicit punning just means that instead of creating a companion object for a type with the same name as the type,
  * we create "companion" implicit conversions with the same name. So that whenever you import the type,
  * you are also always importing the syntax-providing implicit conversions.
  *
  * This happens to be a great fit for Tagless Final Style, since nearly all TF code will import the names of the used typeclasses.
  *
  * Implicit Punning for typeclass syntax relieves the programmer from having to manually import syntax implicits in every file in their codebase.
  *
  * @note The order of conversions is such to allow otherwise conflicting type classes to not conflict,
  *       e.g. code using constraints such as `def x[F[+_, +_]: Functor2: Applicative2: Monad2]` will compile and run
  *       normally when using syntax, despite ambiguity of implicits caused by all 3 implicits inheriting from Functor2.
  *       This is because, due to the priority order being from most-specific to least-specific, the `Monad2` syntax
  *       will be used in such a case, where the `Monad2[F]` implicit is actually unambiguous.
  */
trait Syntax2 extends Syntax2.ImplicitPuns {
  /**
    * A convenient dependent summoner for BIO hierarchy.
    * Auto-narrows to the most powerful available class:
    *
    * {{{
    *   import izumi.functional.bio.{F, Temporal2}
    *
    *   def y[F[+_, +_]: Temporal2] = {
    *     F.timeout(5.seconds)(F.forever(F.unit))
    *   }
    * }}}
    */
  def F[F[+_, +_]](implicit F: Functor2[F]): F.type = F
}

object Syntax2 {

  class FunctorOps[F[+_, +_], +E, +A](protected val r: F[E, A])(implicit protected val F: Functor2[F]) {
    @inline final def map[B](f: A => B): F[E, B] = F.map(r)(f)

    @inline final infix def as[B](b: => B): F[E, B] = F.map(r)(_ => b)
    @inline final def void: F[E, Unit] = F.void(r)
    @inline final def widen[A1](implicit @unused ev: A <:< A1): F[E, A1] = r.asInstanceOf[F[E, A1]]

    @inline final def fromOptionOr[B](valueOnNone: => B)(implicit ev: A <:< Option[B]): F[E, B] = F.fromOptionOr(valueOnNone, widen)
  }

  final class BifunctorOps[F[+_, +_], +E, +A](protected val r: F[E, A])(implicit protected val F: Bifunctor2[F]) {
    @inline final def leftMap[E2](f: E => E2): F[E2, A] = F.leftMap(r)(f)
    @inline final def bimap[E2, B](f: E => E2, g: A => B): F[E2, B] = F.bimap(r)(f, g)

    @inline final def widenError[E1 >: E]: F[E1, A] = r
    @inline final def widenBoth[E1 >: E, A1](implicit @unused ev2: A <:< A1): F[E1, A1] = r.asInstanceOf[F[E1, A1]]
  }

  class ApplicativeOps[F[+_, +_], +E, +A](override protected val r: F[E, A])(implicit override protected val F: Applicative2[F]) extends FunctorOps(r)(F) {

    /** execute two operations in order, return result of second operation */
    @inline final def *>[E1 >: E, B](f0: => F[E1, B]): F[E1, B] = F.*>(r, f0)

    /** execute two operations in order, same as `*>`, but return result of first operation */
    @inline final def <*[E1 >: E, B](f0: => F[E1, B]): F[E1, A] = F.<*(r, f0)

    /** execute two operations in order, return result of both operations */
    @inline final infix def zip[E2 >: E, B, C](r2: => F[E2, B]): F[E2, (A, B)] = F.map2(r, r2)(_ -> _)

    /** execute two operations in order, map their results */
    @inline final def map2[E2 >: E, B, C](r2: => F[E2, B])(f: (A, B) => C): F[E2, C] = F.map2(r, r2)(f)

    @inline final def forever: F[E, Nothing] = F.forever(r)
  }

  class GuaranteeOps[F[+_, +_], +E, +A](override protected val r: F[E, A])(implicit override protected val F: Guarantee2[F]) extends ApplicativeOps(r)(F) {
    @inline final def guarantee(cleanup: F[Nothing, Unit]): F[E, A] = F.guarantee(r, cleanup)
  }

  class ApplicativeErrorOps[F[+_, +_], +E, +A](override protected val r: F[E, A])(implicit override protected val F: ApplicativeError2[F])
    extends GuaranteeOps(r)(F) {
    @inline final def leftMap[E2](f: E => E2): F[E2, A] = F.leftMap(r)(f)
    @inline final def bimap[E2, B](f: E => E2, g: A => B): F[E2, B] = F.bimap(r)(f, g)

    @inline final def orElse[E2, A1 >: A](r2: => F[E2, A1]): F[E2, A1] = F.orElse(r, r2)
    @inline final def leftMap2[E2, A1 >: A, E3](r2: => F[E2, A1])(f: (E, E2) => E3): F[E3, A1] = F.leftMap2(r, r2)(f)

    @inline final def widenError[E1 >: E]: F[E1, A] = r
    @inline final def widenBoth[E1 >: E, A1](implicit @unused ev2: A <:< A1): F[E1, A1] = r.asInstanceOf[F[E1, A1]]
  }

  final class MonadOps[F[+_, +_], +E, +A](override protected val r: F[E, A])(implicit override protected val F: Monad2[F]) extends ApplicativeOps(r)(F) {
    @inline final def flatMap[E1 >: E, B](f0: A => F[E1, B]): F[E1, B] = F.flatMap[E1, A, B](r)(f0)
    @inline final def tap[E1 >: E](f0: A => F[E1, Unit]): F[E1, A] = F.tap(r, f0)

    @inline final def flatten[E1 >: E, A1](implicit ev: A <:< F[E1, A1]): F[E1, A1] = F.flatten(r.widen)

    @inline final def iterateWhile(p: A => Boolean): F[E, A] = F.iterateWhile(r)(p)
    @inline final def iterateUntil(p: A => Boolean): F[E, A] = F.iterateUntil(r)(p)

    @inline final def fromOptionF[E1 >: E, B](fallbackOnNone: => F[E1, B])(implicit ev: A <:< Option[B]): F[E1, B] = F.fromOptionF(fallbackOnNone, r.widen)
  }

  class ErrorOps[F[+_, +_], +E, +A](override protected val r: F[E, A])(implicit override protected val F: Error2[F]) extends ApplicativeErrorOps(r)(F) {
    // duplicated from MonadOps
    @inline final def flatMap[E1 >: E, B](f0: A => F[E1, B]): F[E1, B] = F.flatMap[E1, A, B](r)(f0)
    @inline final def tap[E1 >: E](f0: A => F[E1, Unit]): F[E1, A] = F.tap(r, f0)

    @inline final def flatten[E1 >: E, A1](implicit ev: A <:< F[E1, A1]): F[E1, A1] = F.flatten(r.widen)

    @inline final def iterateWhile(p: A => Boolean): F[E, A] = F.iterateWhile(r)(p)
    @inline final def iterateUntil(p: A => Boolean): F[E, A] = F.iterateUntil(r)(p)

    @inline final def fromOptionF[E1 >: E, B](fallbackOnNone: => F[E1, B])(implicit ev: A <:< Option[B]): F[E1, B] = F.fromOptionF(fallbackOnNone, r.widen)
    // duplicated from MonadOps

    @inline final def catchAll[E2, A2 >: A](h: E => F[E2, A2]): F[E2, A2] = F.catchAll[E, A2, E2](r)(h)
    @inline final def catchSome[E1 >: E, A2 >: A](h: PartialFunction[E, F[E1, A2]]): F[E1, A2] = F.catchSome[E, A2, E1](r)(h)

    @inline final def redeem[E2, B](err: E => F[E2, B], succ: A => F[E2, B]): F[E2, B] = F.redeem[E, A, E2, B](r)(err, succ)
    @inline final def redeemPure[B](err: E => B, succ: A => B): F[Nothing, B] = F.redeemPure(r)(err, succ)

    @inline final def attempt: F[Nothing, Either[E, A]] = F.attempt(r)

    @inline final def tapError[E1 >: E](f: E => F[E1, Unit]): F[E1, A] = F.tapError[E, A, E1](r)(f)

    @inline final def leftFlatMap[E2](f: E => F[Nothing, E2]): F[E2, A] = F.leftFlatMap(r)(f)
    @inline final def flip: F[A, E] = F.flip(r)

    @inline final def tapBoth[E1 >: E, E2 >: E1](err: E => F[E1, Unit])(succ: A => F[E2, Unit]): F[E2, A] = F.tapBoth[E, A, E2](r)(err, succ)

    @inline final def fromEither[E1 >: E, A1](implicit ev: A <:< Either[E1, A1]): F[E1, A1] = F.flatMap[E1, A, A1](r)(F.fromEither[E1, A1](_))
    @inline final def fromOption[E1 >: E, A1](errorOnNone: => E1)(implicit ev1: A <:< Option[A1]): F[E1, A1] = F.fromOption(errorOnNone, r.widen)

    @inline final def retryWhile(f: E => Boolean): F[E, A] = F.retryWhile(r)(f)
    @inline final def retryWhileF(f: E => F[Nothing, Boolean]): F[E, A] = F.retryWhileF(r)(f)

    @inline final def retryUntil(f: E => Boolean): F[E, A] = F.retryUntil(r)(f)
    @inline final def retryUntilF(f: E => F[Nothing, Boolean]): F[E, A] = F.retryUntilF(r)(f)

    /** for-comprehensions sugar:
      *
      * {{{
      *   for {
      *     (1, 2) <- F.pure((2, 1))
      *   } yield ()
      * }}}
      *
      * Use [[widenError]] for pattern matching with non-Throwable errors:
      *
      * {{{
      *   val f = for {
      *     (1, 2) <- F.pure((2, 1)).widenError[Option[Unit]]
      *   } yield ()
      *   // f: F[Option[Unit], Unit] = F.fail(Some(())
      * }}}
      */
    @inline final def withFilter[A1 >: A, E1 >: E](predicate: A => Boolean)(implicit filter: WithFilter[E1], pos: SourceFilePositionMaterializer): F[E1, A] =
      F.withFilter[E1, A](r)(predicate)
  }

  class BracketOps[F[+_, +_], +E, +A](override protected val r: F[E, A])(implicit override protected val F: Bracket2[F]) extends ErrorOps(r)(F) {
    @inline final def bracket[E1 >: E, B](release: A => F[Nothing, Unit])(use: A => F[E1, B]): F[E1, B] = F.bracket(r: F[E1, A])(release)(use)

    @inline final def bracketCase[E1 >: E, B](release: (A, Exit[E1, B]) => F[Nothing, Unit])(use: A => F[E1, B]): F[E1, B] = F.bracketCase(r: F[E1, A])(release)(use)
    @inline final def guaranteeCase(cleanup: Exit[E, A] => F[Nothing, Unit]): F[E, A] = F.guaranteeCase(r, cleanup)

    @inline final def bracketOnFailure[E1 >: E, B](cleanupOnFailure: (A, Exit.Failure[E1]) => F[Nothing, Unit])(use: A => F[E1, B]): F[E1, B] =
      F.bracketOnFailure(r: F[E1, A])(cleanupOnFailure)(use)
    @inline final def guaranteeOnFailure(cleanupOnFailure: Exit.Failure[E] => F[Nothing, Unit]): F[E, A] = F.guaranteeOnFailure(r, cleanupOnFailure)
    @inline final def guaranteeOnInterrupt(cleanupOnInterruption: Exit.Interruption => F[Nothing, Unit]): F[E, A] = F.guaranteeOnInterrupt(r, cleanupOnInterruption)
    @inline final def guaranteeExceptOnInterrupt(cleanupOnNonInterruption: Exit.Uninterrupted[E, A] => F[Nothing, Unit]): F[E, A] =
      F.guaranteeExceptOnInterrupt(r, cleanupOnNonInterruption)
  }

  class PanicOps[F[+_, +_], +E, +A](override protected val r: F[E, A])(implicit override protected val F: Panic2[F]) extends BracketOps(r)(F) {
    @inline final def sandbox: F[Exit.FailureUninterrupted[E], A] = F.sandbox(r)
    @inline final def sandboxExit: F[Nothing, Exit.Uninterrupted[E, A]] = F.sandboxExit(r)

    /**
      * Catch all _defects_ in this effect and convert them to Throwable
      * Example:
      *
      * {{{
      *   F.pure(1)
      *     .map(_ => ???)
      *     .sandboxThrowable
      *     .catchAll(_ => IO2(println("Caught error!")))
      * }}}
      */
    @inline final def sandboxToThrowable(implicit ev: E <:< Throwable): F[Throwable, A] =
      F.leftMap(F.sandbox(r))(_.toThrowable)
    /** Convert Throwable typed error into a defect */
    @inline final def orTerminate(implicit ev: E <:< Throwable): F[Nothing, A] = F.catchAll(r)(F.terminate(_))
    @inline final def uninterruptible: F[E, A] = F.uninterruptible(r)
  }

  class IOOps[F[+_, +_], +E, +A](override protected val r: F[E, A])(implicit override protected val F: IO2[F]) extends PanicOps(r)(F) {
    @inline final def bracketAuto[E1 >: E, B](use: A => F[E1, B])(implicit ev: A <:< AutoCloseable): F[E1, B] =
      F.bracket[E1, A, B](r)(c => F.sync(c.close()))(use)
  }

  class ParallelOps[F[+_, +_], +E, +A](protected val r: F[E, A])(implicit protected val F: Parallel2[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 infix def zipPar[E1 >: E, B](that: F[E1, B]): F[E1, (A, B)] = F.zipPar(r, that)
    @inline final infix def zipParLeft[E1 >: E, B](that: F[E1, B]): F[E1, A] = F.zipParLeft(r, that)
    @inline final infix def zipParRight[E1 >: E, B](that: F[E1, B]): F[E1, B] = F.zipParRight(r, that)
  }
  final class ConcurrentOps[F[+_, +_], +E, +A](override protected val r: F[E, A])(implicit override protected val F: Concurrent2[F])
    extends ParallelOps(r)(F) {
    @inline final infix def race[E1 >: E, A1 >: A](that: F[E1, A1]): F[E1, A1] = F.race(r, that)
    @inline final def racePairUnsafe[E1 >: E, A1 >: A](
      that: F[E1, A1]
    ): F[E1, Either[(Exit[E1, A], Fiber2[F, E1, A1]), (Fiber2[F, E1, A], Exit[E1, A1])]] = F.racePairUnsafe(r, that)
  }
  class AsyncOps[F[+_, +_], +E, +A](override protected val r: F[E, A])(implicit override protected val F: Async2[F]) extends IOOps(r)(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 infix def zipPar[E1 >: E, B](that: F[E1, B]): F[E1, (A, B)] = F.zipPar(r, that)
    @inline final infix def zipParLeft[E1 >: E, B](that: F[E1, B]): F[E1, A] = F.zipParLeft(r, that)
    @inline final infix def zipParRight[E1 >: E, B](that: F[E1, B]): F[E1, B] = F.zipParRight(r, that)

    @inline final infix def race[E1 >: E, A1 >: A](that: F[E1, A1]): F[E1, A1] = F.race(r, that)
    @inline final def racePairUnsafe[E1 >: E, A1 >: A](
      that: F[E1, A1]
    ): F[E1, Either[(Exit[E1, A], Fiber2[F, E1, A1]), (Fiber2[F, E1, A], Exit[E1, A1])]] = F.racePairUnsafe(r, that)
  }

  final class TemporalOps[F[+_, +_], +E, +A](protected val r: F[E, A])(implicit protected val F: Temporal2[F]) {
    @inline final def repeatUntil[E2 >: E, A2](tooManyAttemptsError: => E2, sleep: FiniteDuration, maxAttempts: Int)(implicit ev: A <:< Option[A2]): F[E2, A2] =
      F.repeatUntil[E2, A2](new FunctorOps(r)(F.InnerF).widen)(tooManyAttemptsError, sleep, maxAttempts)

    @inline final def timeout(duration: Duration): F[E, Option[A]] = F.timeout(duration)(r)
    @inline final def timeoutFail[E1 >: E](e: => E1)(duration: Duration): F[E1, A] = F.timeoutFail(duration)(e, r)
  }

  final class ForkOps[F[+_, +_], +E, +A](private val r: F[E, A])(implicit private val F: Fork2[F]) {
    @inline final def fork: F[Nothing, Fiber2[F, E, A]] = F.fork(r)
  }

  trait ImplicitPuns extends ImplicitPuns1 {
    @inline implicit final def Temporal2[F[+_, +_]: Temporal2, E, A](self: F[E, A]): TemporalOps[F, E, A] = new TemporalOps[F, E, A](self)
    @inline implicit final def Temporal2[F[+_, +_]: Error2, E, A](self: F[E, A]): ErrorOps[F, E, A] = new ErrorOps[F, E, A](self)
    @inline final def Temporal2[F[+_, +_]: Temporal2]: Temporal2[F] = implicitly

    @inline implicit final def Fork2[F[+_, +_]: Fork2, E, A](self: F[E, A]): ForkOps[F, E, A] = new ForkOps[F, E, A](self)
    @inline final def Fork2[F[+_, +_]: Fork2]: Fork2[F] = implicitly
  }
  trait ImplicitPuns1 extends ImplicitPuns2 {
    @inline implicit final def Async2[F[+_, +_]: Async2, E, A](self: F[E, A]): AsyncOps[F, E, A] = new AsyncOps[F, E, A](self)
    @inline final def Async2[F[+_, +_]: Async2]: Async2[F] = implicitly
  }
  trait ImplicitPuns2 extends ImplicitPuns3 {
    @inline implicit final def Concurrent2[F[+_, +_]: Concurrent2, E, A](self: F[E, A]): ConcurrentOps[F, E, A] = new ConcurrentOps[F, E, A](self)
    @inline implicit final def Concurrent2[F[+_, +_]: Panic2, E, A](self: F[E, A]): PanicOps[F, E, A] = new PanicOps[F, E, A](self)
    @inline final def Concurrent2[F[+_, +_]: Concurrent2]: Concurrent2[F] = implicitly
  }
  trait ImplicitPuns3 extends ImplicitPuns4 {
    @inline implicit final def Parallel2[F[+_, +_]: Parallel2, E, A](self: F[E, A]): ParallelOps[F, E, A] = new ParallelOps[F, E, A](self)
    @inline implicit final def Parallel2[F[+_, +_]: Monad2, E, A](self: F[E, A]): MonadOps[F, E, A] = new MonadOps[F, E, A](self)
    @inline final def Parallel2[F[+_, +_]: Parallel2]: Parallel2[F] = implicitly
  }
  trait ImplicitPuns4 extends ImplicitPuns5 {
    @inline implicit final def IO2[F[+_, +_]: IO2, E, A](self: F[E, A]): IOOps[F, E, A] = new IOOps[F, E, A](self)
    /**
      * Shorthand for [[IO2#syncThrowable]]
      *
      * {{{
      *   IO2(println("Hello world!"))
      * }}}
      */
    @inline final def IO2[F[+_, +_], A](effect: => A)(implicit F: IO2[F]): F[Throwable, A] = F.syncThrowable(effect)
    @inline final def IO2[F[+_, +_]: IO2]: IO2[F] = implicitly
  }
  trait ImplicitPuns5 extends ImplicitPuns6 {
    @inline implicit final def Panic2[F[+_, +_]: Panic2, E, A](self: F[E, A]): PanicOps[F, E, A] = new PanicOps[F, E, A](self)
    @inline final def Panic2[F[+_, +_]: Panic2]: Panic2[F] = implicitly
  }
  trait ImplicitPuns6 extends ImplicitPuns7 {
    @inline implicit final def Bracket2[F[+_, +_]: Bracket2, E, A](self: F[E, A]): BracketOps[F, E, A] = new BracketOps[F, E, A](self)
    @inline final def Bracket2[F[+_, +_]: Bracket2]: Bracket2[F] = implicitly
  }
  trait ImplicitPuns7 extends ImplicitPuns8 {
    @inline implicit final def Error2[F[+_, +_]: Error2, E, A](self: F[E, A]): ErrorOps[F, E, A] = new ErrorOps[F, E, A](self)
    @inline final def Error2[F[+_, +_]: Error2]: Error2[F] = implicitly
  }
  trait ImplicitPuns8 extends ImplicitPuns9 {
    @inline implicit final def ApplicativeError2[F[+_, +_]: ApplicativeError2, E, A](self: F[E, A]): ApplicativeErrorOps[F, E, A] = new ApplicativeErrorOps[F, E, A](self)
    @inline final def ApplicativeError2[F[+_, +_]: ApplicativeError2]: ApplicativeError2[F] = implicitly
  }
  trait ImplicitPuns9 extends ImplicitPuns10 {
    @inline implicit final def Guarantee2[F[+_, +_]: Guarantee2, E, A](self: F[E, A]): GuaranteeOps[F, E, A] = new GuaranteeOps[F, E, A](self)
    @inline final def Guarantee2[F[+_, +_]: Guarantee2]: Guarantee2[F] = implicitly
  }
  trait ImplicitPuns10 extends ImplicitPuns11 {
    @inline implicit final def Monad2[F[+_, +_]: Monad2, E, A](self: F[E, A]): MonadOps[F, E, A] = new MonadOps[F, E, A](self)
    @inline final def Monad2[F[+_, +_]: Monad2]: Monad2[F] = implicitly
  }
  trait ImplicitPuns11 extends ImplicitPuns12 {
    @inline implicit final def Applicative2[F[+_, +_]: Applicative2, E, A](self: F[E, A]): ApplicativeOps[F, E, A] = new ApplicativeOps[F, E, A](self)
    @inline final def Applicative2[F[+_, +_]: Applicative2]: Applicative2[F] = implicitly
  }
  trait ImplicitPuns12 extends ImplicitPuns13 {
    @inline implicit final def Bifunctor2[F[+_, +_]: Bifunctor2, E, A](self: F[E, A]): BifunctorOps[F, E, A] = new BifunctorOps[F, E, A](self)
    @inline implicit final def Bifunctor2[F[+_, +_]: Functor2, E, A](self: F[E, A]): FunctorOps[F, E, A] = new FunctorOps[F, E, A](self)
    @inline final def Bifunctor2[F[+_, +_]: Bifunctor2]: Bifunctor2[F] = implicitly
  }
  trait ImplicitPuns13 {
    @inline implicit final def Functor2[F[+_, +_]: Functor2, E, A](self: F[E, A]): FunctorOps[F, E, A] = new FunctorOps[F, E, A](self)
    @inline final def Functor2[F[+_, +_]: Functor2]: Functor2[F] = implicitly
  }

  final class ClockAccessor[F[+_, +_]](@unused private val dummy: Boolean = false) extends AnyVal {
    def clock(implicit clock: Clock2[F]): clock.type = clock
  }

  final class EntropyAccessor[F[+_, +_]](@unused private val dummy: Boolean = false) extends AnyVal {
    def entropy(implicit entropy: Entropy2[F]): entropy.type = entropy
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy