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

monix.eval.Coeval.scala Maven / Gradle / Ivy

/*
 * Copyright (c) 2014-2016 by its authors. Some rights reserved.
 * See the project homepage at: https://monix.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package monix.eval

import monix.types._
import monix.eval.Coeval._
import scala.annotation.tailrec
import scala.collection.mutable
import scala.collection.generic.CanBuildFrom
import scala.util.control.NonFatal
import scala.util.{Failure, Success, Try}

/** `Coeval` represents lazy computations that can execute synchronously.
  *
  * Word definition and origin:
  *
  *  - Having the same age or date of origin; a contemporary; synchronous.
  *  - From the Latin "coævus": com- ‎("equal") in combination with aevum ‎(aevum, "age").
  *  - The constructor of `Coeval` is the dual of an expression that evaluates to an `A`.
  *
  * There are three evaluation strategies:
  *
  *  - [[monix.eval.Coeval.Now Now]] or [[monix.eval.Coeval.Error Error]]:
  *    for describing strict values, evaluated immediately
  *  - [[monix.eval.Coeval.Once Once]]: expressions evaluated a single time
  *  - [[monix.eval.Coeval.Always Always]]: expressions evaluated every time
  *    the value is needed
  *
  * The `Once` and `Always` are both lazy strategies while
  * `Now` and `Error` are eager. `Once` and `Always` are
  * distinguished from each other only by memoization: once evaluated
  * `Once` will save the value to be returned immediately if it is
  * needed again. `Always` will run its computation every time.
  *
  * Both `Now` and `Error` are represented by the
  * [[monix.eval.Coeval.Attempt Attempt]] trait, a sub-type of [[Coeval]]
  * that can be used as a replacement for Scala's own `Try` type.
  *
  * `Coeval` supports stack-safe lazy computation via the .map and .flatMap
  * methods, which use an internal trampoline to avoid stack overflows.
  * Computation done within .map and .flatMap is always done lazily,
  * even when applied to a `Now` instance.
  */
sealed abstract class Coeval[+A] extends Serializable { self =>
  /** Evaluates the underlying computation and returns the result.
    *
    * NOTE: this can throw exceptions.
    */
  def value: A = runAttempt match {
    case Now(value) => value
    case Error(ex) => throw ex
  }

  /** Evaluates the underlying computation and returns the
    * result or any triggered errors as a [[Coeval.Attempt]].
    */
  def runAttempt: Attempt[A] =
    Coeval.trampoline(this, Nil)

  /** Evaluates the underlying computation and returns the
    * result or any triggered errors as a `scala.util.Try`.
    */
  def runTry: Try[A] =
    Coeval.trampoline(this, Nil).asScala

  /** Converts the source [[Coeval]] into a [[Task]]. */
  def task: Task[A] =
    Task.coeval(self)

  /** Creates a new `Coeval` by applying a function to the successful result
    * of the source, and returns a new instance equivalent
    * to the result of the function.
    */
  def flatMap[B](f: A => Coeval[B]): Coeval[B] =
    self match {
      case Now(a) =>
        Suspend(() => try f(a) catch { case NonFatal(ex) => Error(ex) })
      case eval @ Once(_) =>
        Suspend(() => eval.runAttempt match {
          case Now(a) => try f(a) catch { case NonFatal(ex) => Error(ex) }
          case error @ Error(_) => error
        })
      case Always(thunk) =>
        Suspend(() => try f(thunk()) catch {
          case NonFatal(ex) => Error(ex)
        })
      case Suspend(thunk) =>
        BindSuspend(thunk, f)
      case BindSuspend(thunk, g) =>
        Suspend(() => BindSuspend(thunk, g andThen (_ flatMap f)))
      case error @ Error(_) =>
        error
    }

  /** Given a source Coeval that emits another Coeval, this function
    * flattens the result, returning a Coeval equivalent to the emitted
    * Coeval by the source.
    */
  def flatten[B](implicit ev: A <:< Coeval[B]): Coeval[B] =
    flatMap(a => a)

  /** Returns a failed projection of this coeval.
    *
    * The failed projection is a future holding a value of type
    * `Throwable`, emitting a value which is the throwable of the
    * original coeval in case the original coeval fails, otherwise if the
    * source succeeds, then it fails with a `NoSuchElementException`.
    */
  def failed: Coeval[Throwable] =
    materializeAttempt.flatMap {
      case Error(ex) => Now(ex)
      case Now(_) => Error(new NoSuchElementException("failed"))
    }

  /** Returns a new Coeval that applies the mapping function to
    * the element emitted by the source.
    */
  def map[B](f: A => B): Coeval[B] =
    flatMap(a => try Now(f(a)) catch { case NonFatal(ex) => Error(ex) })


  /** Creates a new [[Coeval]] that will expose any triggered error from
    * the source.
    */
  def materialize: Coeval[Try[A]] =
    materializeAttempt.map(_.asScala)

  /** Creates a new [[Coeval]] that will expose any triggered error from
    * the source.
    */
  def materializeAttempt: Coeval[Attempt[A]] =
    self match {
      case now @ Now(_) =>
        Now(now)
      case eval @ Once(_) =>
        Suspend(() => Now(eval.runAttempt))
      case Always(thunk) =>
        Suspend(() => Now(try Now(thunk()) catch { case NonFatal(ex) => Error(ex) }))
      case Error(ex) =>
        Now(Error(ex))
      case Suspend(thunk) =>
        Suspend(() => try thunk().materializeAttempt catch { case NonFatal(ex) => Now(Error(ex)) })
      case BindSuspend(thunk, g) =>
        BindSuspend[Attempt[Any], Attempt[A]](
          () => try thunk().materializeAttempt catch { case NonFatal(ex) => Now(Error(ex)) },
          result => result match {
            case Now(any) =>
              try { g.asInstanceOf[Any => Coeval[A]](any).materializeAttempt }
              catch { case NonFatal(ex) => Now(Error(ex)) }
            case Error(ex) =>
              Now(Error(ex))
          })
    }

  /** Dematerializes the source's result from a `Try`. */
  def dematerialize[B](implicit ev: A <:< Try[B]): Coeval[B] =
    self.asInstanceOf[Coeval[Try[B]]].flatMap(Attempt.fromTry)

  /** Dematerializes the source's result from an `Attempt`. */
  def dematerializeAttempt[B](implicit ev: A <:< Attempt[B]): Coeval[B] =
    self.asInstanceOf[Coeval[Attempt[B]]].flatMap(identity)

  /** Given a predicate function, keep retrying the
    * coeval until the function returns true.
    */
  def restartUntil(p: (A) => Boolean): Coeval[A] =
    self.flatMap(a => if (p(a)) Coeval.now(a) else self.restartUntil(p))

  /** Creates a new coeval that will try recovering from an error by
    * matching it with another coeval using the given partial function.
    *
    * See [[onErrorHandleWith]] for the version that takes a total function.
    */
  def onErrorRecoverWith[B >: A](pf: PartialFunction[Throwable, Coeval[B]]): Coeval[B] =
    onErrorHandleWith(ex => pf.applyOrElse(ex, Coeval.raiseError))

  /** Creates a new coeval that will handle any matching throwable that
    * this coeval might emit by executing another coeval.
    *
    * See [[onErrorRecoverWith]] for the version that takes a partial function.
    */
  def onErrorHandleWith[B >: A](f: Throwable => Coeval[B]): Coeval[B] =
    self.materializeAttempt.flatMap {
      case now @ Now(_) => now
      case Error(ex) => try f(ex) catch { case NonFatal(err) => Error(err) }
    }

  /** Creates a new coeval that in case of error will fallback to the
    * given backup coeval.
    */
  def onErrorFallbackTo[B >: A](that: Coeval[B]): Coeval[B] =
    onErrorHandleWith(ex => that)

  /** Creates a new coeval that in case of error will retry executing the
    * source again and again, until it succeeds.
    *
    * In case of continuous failure the total number of executions
    * will be `maxRetries + 1`.
    */
  def onErrorRestart(maxRetries: Long): Coeval[A] =
    self.onErrorHandleWith(ex =>
      if (maxRetries > 0) self.onErrorRestart(maxRetries-1)
      else Error(ex))

  /** Creates a new coeval that in case of error will retry executing the
    * source again and again, until it succeeds.
    *
    * In case of continuous failure the total number of executions
    * will be `maxRetries + 1`.
    */
  def onErrorRestartIf(p: Throwable => Boolean): Coeval[A] =
    self.onErrorHandleWith(ex => if (p(ex)) self.onErrorRestartIf(p) else Error(ex))

  /** Creates a new coeval that will handle any matching throwable that
    * this coeval might emit.
    *
    * See [[onErrorRecover]] for the version that takes a partial function.
    */
  def onErrorHandle[U >: A](f: Throwable => U): Coeval[U] =
    onErrorHandleWith(ex => try Now(f(ex)) catch { case NonFatal(err) => Error(err) })

  /** Creates a new coeval that on error will try to map the error
    * to another value using the provided partial function.
    *
    * See [[onErrorHandle]] for the version that takes a total function.
    */
  def onErrorRecover[U >: A](pf: PartialFunction[Throwable, U]): Coeval[U] =
    onErrorRecoverWith(pf.andThen(Coeval.now))

  /** Memoizes the result on the computation and reuses it on subsequent
    * invocations of `runAsync`.
    */
  def memoize: Coeval[A] =
    self match {
      case ref @ Now(_) => ref
      case error @ Error(_) => error
      case Always(thunk) =>
        new Once[A](thunk)
      case eval: Once[_] => self
      case other =>
        new Once[A](() => other.value)
    }

  /** Returns a new `Coeval` in which `f` is scheduled to be run on completion.
    * This would typically be used to release any resources acquired by this
    * `Coeval`.
    */
  def doOnFinish(f: Option[Throwable] => Coeval[Unit]): Coeval[A] =
    materializeAttempt.flatMap {
      case Coeval.Now(value) =>
        f(None).map(_ => value)
      case Coeval.Error(ex) =>
        f(Some(ex)).flatMap(_ => Coeval.raiseError(ex))
    }

  /** Zips the values of `this` and `that` coeval, and creates a new coeval
    * that will emit the tuple of their results.
    */
  def zip[B](that: Coeval[B]): Coeval[(A, B)] =
    for (a <- this; b <- that) yield (a,b)

  /** Zips the values of `this` and `that` and applies the given
    * mapping function on their results.
    */
  def zipMap[B,C](that: Coeval[B])(f: (A,B) => C): Coeval[C] =
    for (a <- this; b <- that) yield f(a,b)

  @deprecated("Renamed, please use Coeval.zipMap", since="2.0-RC12")
  def zipWith[B,C](that: Coeval[B])(f: (A,B) => C): Coeval[C] =
    for (a <- this; b <- that) yield f(a,b)
}

object Coeval {
  /** Promotes a non-strict value to a [[Coeval]].
    *
    * Alias of [[eval]].
    */
  def apply[A](f: => A): Coeval[A] =
    Always(f _)

  /** Returns an `Coeval` that on execution is always successful, emitting
    * the given strict value.
    */
  def now[A](a: A): Coeval[A] = Now(a)

  /** Lifts a value into the coeval context. Alias for [[now]]. */
  def pure[A](a: A): Coeval[A] = Now(a)

  /** Returns an `Coeval` that on execution is always finishing in error
    * emitting the specified exception.
    */
  def raiseError[A](ex: Throwable): Coeval[A] =
    Error(ex)

  /** Promote a non-strict value representing a `Coeval`
    * to a `Coeval` of the same type.
    */
  def defer[A](fa: => Coeval[A]): Coeval[A] =
    Suspend(() => fa)

  /** Alias for [[defer]]. */
  def suspend[A](fa: => Coeval[A]): Coeval[A] = defer(fa)

  /** Promote a non-strict value to a `Coeval` that is memoized on the first
    * evaluation, the result being then available on subsequent evaluations.
    */
  def evalOnce[A](a: => A): Coeval[A] = Once(a _)

  /** Promote a non-strict value to an `Coeval`, catching exceptions in the
    * process.
    *
    * Note that since `Coeval` is not memoized, this will recompute the
    * value each time the `Coeval` is executed.
    */
  def eval[A](a: => A): Coeval[A] = Always(a _)

  /** Alias for [[eval]]. */
  def delay[A](a: => A): Coeval[A] = eval(a)

  /** Alias for [[eval]]. Deprecated. */
  @deprecated("Renamed, please use Coeval.apply or Coeval.eval", since="2.0-RC12")
  def evalAlways[A](a: => A): Coeval[A] = eval(a)

  /** A `Coeval[Unit]` provided for convenience. */
  val unit: Coeval[Unit] = Now(())

  /** Builds a `Coeval` out of a Scala `Try` value. */
  def fromTry[A](a: Try[A]): Coeval[A] =
    Attempt.fromTry(a)

  /** Transforms a `TraversableOnce` of coevals into a coeval producing
    * the same collection of gathered results.
    *
    * It's a simple version of [[traverse]].
    */
  def sequence[A, M[X] <: TraversableOnce[X]](sources: M[Coeval[A]])
    (implicit cbf: CanBuildFrom[M[Coeval[A]], A, M[A]]): Coeval[M[A]] = {
    val init = eval(cbf(sources))
    val r = sources.foldLeft(init)((acc,elem) => acc.zipMap(elem)(_ += _))
    r.map(_.result())
  }

  /** Transforms a `TraversableOnce[A]` into a coeval of the same collection
    * using the provided function `A => Coeval[B]`.
    *
    * It's a generalized version of [[sequence]].
    */
  def traverse[A, B, M[X] <: TraversableOnce[X]](sources: M[A])(f: A => Coeval[B])
    (implicit cbf: CanBuildFrom[M[A], B, M[B]]): Coeval[M[B]] = {
    val init = eval(cbf(sources))
    val r = sources.foldLeft(init)((acc,elem) => acc.zipMap(f(elem))(_ += _))
    r.map(_.result())
  }

  /** Zips together multiple [[Coeval]] instances. */
  def zipList[A](sources: Coeval[A]*): Coeval[List[A]] = {
    val init = eval(mutable.ListBuffer.empty[A])
    val r = sources.foldLeft(init)((acc, elem) => acc.zipMap(elem)(_ += _))
    r.map(_.toList)
  }

  /** Pairs two [[Coeval]] instances. */
  def zip2[A1, A2, R](fa1: Coeval[A1], fa2: Coeval[A2]): Coeval[(A1, A2)] =
    fa1.zipMap(fa2)((_, _))

  /** Pairs two [[Coeval]] instances, creating a new instance that will apply
    * the given mapping function to the resulting pair. */
  def zipMap2[A1, A2, R](fa1: Coeval[A1], fa2: Coeval[A2])(f: (A1, A2) => R): Coeval[R] =
    fa1.zipMap(fa2)(f)

  /** Pairs three [[Coeval]] instances. */
  def zip3[A1, A2, A3](fa1: Coeval[A1], fa2: Coeval[A2], fa3: Coeval[A3]): Coeval[(A1, A2, A3)] =
    zipMap3(fa1, fa2, fa3)((a1, a2, a3) => (a1, a2, a3))

  /** Pairs four [[Coeval]] instances. */
  def zip4[A1, A2, A3, A4](fa1: Coeval[A1], fa2: Coeval[A2], fa3: Coeval[A3], fa4: Coeval[A4]): Coeval[(A1, A2, A3, A4)] =
    zipMap4(fa1, fa2, fa3, fa4)((a1, a2, a3, a4) => (a1, a2, a3, a4))

  /** Pairs five [[Coeval]] instances. */
  def zip5[A1, A2, A3, A4, A5](fa1: Coeval[A1], fa2: Coeval[A2], fa3: Coeval[A3], fa4: Coeval[A4], fa5: Coeval[A5]): Coeval[(A1, A2, A3, A4, A5)] =
    zipMap5(fa1, fa2, fa3, fa4, fa5)((a1, a2, a3, a4, a5) => (a1, a2, a3, a4, a5))

  /** Pairs six [[Coeval]] instances. */
  def zip6[A1, A2, A3, A4, A5, A6](fa1: Coeval[A1], fa2: Coeval[A2], fa3: Coeval[A3], fa4: Coeval[A4], fa5: Coeval[A5], fa6: Coeval[A6]): Coeval[(A1, A2, A3, A4, A5, A6)] =
    zipMap6(fa1, fa2, fa3, fa4, fa5, fa6)((a1, a2, a3, a4, a5, a6) => (a1, a2, a3, a4, a5, a6))

  /** Pairs three [[Coeval]] instances,
    * applying the given mapping function to the result.
    */
  def zipMap3[A1, A2, A3, R](fa1: Coeval[A1], fa2: Coeval[A2], fa3: Coeval[A3])
    (f: (A1, A2, A3) => R): Coeval[R] = {

    val fa12 = zip2(fa1, fa2)
    zipMap2(fa12, fa3) { case ((a1, a2), a3) => f(a1, a2, a3) }
  }

  /** Pairs four [[Coeval]] instances,
    * applying the given mapping function to the result.
    */
  def zipMap4[A1, A2, A3, A4, R](fa1: Coeval[A1], fa2: Coeval[A2], fa3: Coeval[A3], fa4: Coeval[A4])
    (f: (A1, A2, A3, A4) => R): Coeval[R] = {

    val fa123 = zip3(fa1, fa2, fa3)
    zipMap2(fa123, fa4) { case ((a1, a2, a3), a4) => f(a1, a2, a3, a4) }
  }

  /** Pairs five [[Coeval]] instances,
    * applying the given mapping function to the result.
    */
  def zipMap5[A1, A2, A3, A4, A5, R](fa1: Coeval[A1], fa2: Coeval[A2], fa3: Coeval[A3], fa4: Coeval[A4], fa5: Coeval[A5])
    (f: (A1, A2, A3, A4, A5) => R): Coeval[R] = {

    val fa1234 = zip4(fa1, fa2, fa3, fa4)
    zipMap2(fa1234, fa5) { case ((a1, a2, a3, a4), a5) => f(a1, a2, a3, a4, a5) }
  }

  /** Pairs six [[Coeval]] instances,
    * applying the given mapping function to the result.
    */
  def zipMap6[A1, A2, A3, A4, A5, A6, R](fa1: Coeval[A1], fa2: Coeval[A2], fa3: Coeval[A3], fa4: Coeval[A4], fa5: Coeval[A5], fa6: Coeval[A6])
    (f: (A1, A2, A3, A4, A5, A6) => R): Coeval[R] = {

    val fa12345 = zip5(fa1, fa2, fa3, fa4, fa5)
    zipMap2(fa12345, fa6) { case ((a1, a2, a3, a4, a5), a6) => f(a1, a2, a3, a4, a5, a6) }
  }

  @deprecated("Renamed to Coeval.zipMap2", since="2.0-RC12")
  def zipWith2[A1,A2,R](fa1: Coeval[A1], fa2: Coeval[A2])(f: (A1,A2) => R): Coeval[R] =
    zipMap2(fa1, fa2)(f)

  @deprecated("Renamed to Coeval.zipMap3", since="2.0-RC12")
  def zipWith3[A1,A2,A3,R](fa1: Coeval[A1], fa2: Coeval[A2], fa3: Coeval[A3])(f: (A1,A2,A3) => R): Coeval[R] =
    zipMap3(fa1, fa2, fa3)(f)

  @deprecated("Renamed to Coeval.zipMap4", since="2.0-RC12")
  def zipWith4[A1,A2,A3,A4,R](fa1: Coeval[A1], fa2: Coeval[A2], fa3: Coeval[A3], fa4: Coeval[A4])(f: (A1,A2,A3,A4) => R): Coeval[R] =
    zipMap4(fa1, fa2, fa3, fa4)(f)

  @deprecated("Renamed to Coeval.zipMap5", since="2.0-RC12")
  def zipWith5[A1,A2,A3,A4,A5,R](fa1: Coeval[A1], fa2: Coeval[A2], fa3: Coeval[A3], fa4: Coeval[A4], fa5: Coeval[A5])(f: (A1,A2,A3,A4,A5) => R): Coeval[R] =
    zipMap5(fa1, fa2, fa3, fa4, fa5)(f)

  @deprecated("Renamed to Coeval.zipMap6", since="2.0-RC12")
  def zipWith6[A1,A2,A3,A4,A5,A6,R](fa1: Coeval[A1], fa2: Coeval[A2], fa3: Coeval[A3], fa4: Coeval[A4], fa5: Coeval[A5], fa6: Coeval[A6])(f: (A1,A2,A3,A4,A5,A6) => R): Coeval[R] =
    zipMap6(fa1, fa2, fa3, fa4, fa5, fa6)(f)


  /** The `Attempt` represents a strict, already evaluated result
    * of a [[Coeval]] that either resulted in success, wrapped in a
    * [[Now]], or in an error, wrapped in a [[Error]].
    *
    * It's the moral equivalent of `scala.util.Try`.
    */
  sealed abstract class Attempt[+A] extends Coeval[A] with Product {
    self =>
    /** Returns true if value is a successful one. */
    def isSuccess: Boolean = this match {
      case Now(_) => true
      case _ => false
    }

    /** Returns true if result is an error. */
    def isFailure: Boolean = this match {
      case Error(_) => true
      case _ => false
    }

    override def failed: Attempt[Throwable] =
      self match {
        case Now(_) => Error(new NoSuchElementException("failed"))
        case Error(ex) => Now(ex)
      }

    /** Converts this attempt into a `scala.util.Try`. */
    def asScala: Try[A] =
      this match {
        case Now(a) => Success(a)
        case Error(ex) => Failure(ex)
      }

    override def materializeAttempt: Attempt[Attempt[A]] =
      self match {
        case now@Now(_) =>
          Now(now)
        case Error(ex) =>
          Now(Error(ex))
      }

    override def dematerializeAttempt[B](implicit ev: <:<[A, Attempt[B]]): Attempt[B] =
      self match {
        case Now(now) => now
        case error@Error(_) => error
      }

    override def memoize: Attempt[A] = this
  }

  object Attempt {
    /** Promotes a non-strict value to a [[Coeval.Attempt]]. */
    def apply[A](f: => A): Attempt[A] =
      try Now(f) catch {
        case NonFatal(ex) => Error(ex)
      }

    /** Builds an [[Coeval.Attempt Attempt]] from a `scala.util.Try` */
    def fromTry[A](value: Try[A]): Attempt[A] =
      value match {
        case Success(a) => Now(a)
        case Failure(ex) => Error(ex)
      }
  }

  /** Constructs an eager [[Coeval]] instance from a strict
    * value that's already known.
    */
  final case class Now[+A](override val value: A) extends Attempt[A] {
    override def runAttempt: Now[A] = this
  }

  /** Constructs an eager [[Coeval]] instance for
    * a result that represents an error.
    */
  final case class Error(ex: Throwable) extends Attempt[Nothing] {
    override def value: Nothing = throw ex

    override def runAttempt: Error = this
  }

  @deprecated("Type renamed, use Eval.Once", since="2.0-RC12")
  type EvalOnce[+A] = Once[A]

  /** Constructs a lazy [[Coeval]] instance that gets evaluated
    * only once.
    *
    * In some sense it is equivalent to using a lazy val.
    * When caching is not required or desired,
    * prefer [[Always]] or [[Now]].
    */
  final class Once[+A](f: () => A) extends Coeval[A] with (() => A) {
    private[this] var thunk: () => A = f

    def apply(): A = runAttempt match {
      case Now(a) => a
      case Error(ex) => throw ex
    }

    override lazy val runAttempt: Attempt[A] = {
      try {
        Now(thunk())
      } catch {
        case NonFatal(ex) => Error(ex)
      } finally {
        // GC relief
        thunk = null
      }
    }

    override def toString =
      synchronized {
        if (thunk != null) s"Once($thunk)"
        else s"Once($runAttempt)"
      }
  }

  object Once {
    /** Builder for an [[Once]] instance. */
    def apply[A](a: () => A): Once[A] =
      new Once[A](a)

    /** Deconstructs an [[Once]] instance. */
    def unapply[A](coeval: Once[A]): Some[() => A] =
      Some(coeval)
  }

  @deprecated("Type renamed, use Eval.Always", since="2.0-RC12")
  type EvalAlways[+A] = Always[A]

  /** Constructs a lazy [[Coeval]] instance.
    *
    * This type can be used for "lazy" values. In some sense it is
    * equivalent to using a Function0 value.
    */
  final case class Always[+A](f: () => A) extends Coeval[A] {
    override def value: A = f()

    override def runAttempt: Attempt[A] =
      try Now(f()) catch {
        case NonFatal(ex) => Error(ex)
      }
  }

  /** Internal state, the result of [[Coeval.defer]] */
  private[eval] final case class Suspend[+A](thunk: () => Coeval[A]) extends Coeval[A]

  /** Internal [[Coeval]] state that is the result of applying `flatMap`. */
  private[eval] final case class BindSuspend[A, B](thunk: () => Coeval[A], f: A => Coeval[B]) extends Coeval[B]

  private type Current = Coeval[Any]
  private type Bind = Any => Coeval[Any]

  /** Trampoline for lazy evaluation. */
  private[eval] def trampoline[A](source: Coeval[A], binds: List[Bind]): Attempt[A] = {
    @tailrec def reduceCoeval(source: Coeval[Any], binds: List[Bind]): Attempt[Any] = {
      source match {
        case error@Error(_) => error
        case now@Now(a) =>
          binds match {
            case Nil => now
            case f :: rest =>
              val fa = try f(a) catch {
                case NonFatal(ex) => Error(ex)
              }
              reduceCoeval(fa, rest)
          }

        case eval@Once(_) =>
          eval.runAttempt match {
            case now@Now(a) =>
              binds match {
                case Nil => now
                case f :: rest =>
                  val fa = try f(a) catch {
                    case NonFatal(ex) => Error(ex)
                  }
                  reduceCoeval(fa, rest)
              }
            case error@Error(_) =>
              error
          }

        case Always(thunk) =>
          val fa = try Now(thunk()) catch {
            case NonFatal(ex) => Error(ex)
          }
          reduceCoeval(fa, binds)

        case Suspend(thunk) =>
          val fa = try thunk() catch {
            case NonFatal(ex) => Error(ex)
          }
          reduceCoeval(fa, binds)

        case BindSuspend(thunk, f) =>
          val fa = try thunk() catch {
            case NonFatal(ex) => Error(ex)
          }
          reduceCoeval(fa, f.asInstanceOf[Bind] :: binds)
      }
    }

    reduceCoeval(source, Nil).asInstanceOf[Attempt[A]]
  }

  /** Implicit type-class instances of [[Coeval]]. */
  implicit val typeClassInstances: TypeClassInstances = new TypeClassInstances

  /** Groups the implementation for the type-classes defined in [[monix.types]]. */
  class TypeClassInstances
    extends DeferrableClass[Coeval]
    with MemoizableClass[Coeval]
    with RecoverableClass[Coeval,Throwable]
    with ComonadClass[Coeval]
    with MonadRecClass[Coeval] {

    override def pure[A](a: A): Coeval[A] = Coeval.now(a)
    override def defer[A](fa: => Coeval[A]): Coeval[A] = Coeval.defer(fa)
    override def evalOnce[A](a: => A): Coeval[A] = Coeval.evalOnce(a)
    override def eval[A](a: => A): Coeval[A] = Coeval.eval(a)
    override def memoize[A](fa: Coeval[A]): Coeval[A] = fa.memoize

    override def extract[A](x: Coeval[A]): A =
      x.value
    override def flatMap[A, B](fa: Coeval[A])(f: (A) => Coeval[B]): Coeval[B] =
      fa.flatMap(f)
    override def flatten[A](ffa: Coeval[Coeval[A]]): Coeval[A] =
      ffa.flatten
    override def coflatMap[A, B](fa: Coeval[A])(f: (Coeval[A]) => B): Coeval[B] =
      Coeval.eval(f(fa))
    override def ap[A, B](ff: Coeval[(A) => B])(fa: Coeval[A]): Coeval[B] =
      for (f <- ff; a <- fa) yield f(a)
    override def map2[A, B, Z](fa: Coeval[A], fb: Coeval[B])(f: (A, B) => Z): Coeval[Z] =
      for (a <- fa; b <- fb) yield f(a, b)
    override def map[A, B](fa: Coeval[A])(f: (A) => B): Coeval[B] =
      fa.map(f)
    override def raiseError[A](e: Throwable): Coeval[A] =
      Coeval.raiseError(e)
    override def onErrorHandle[A](fa: Coeval[A])(f: (Throwable) => A): Coeval[A] =
      fa.onErrorHandle(f)
    override def onErrorHandleWith[A](fa: Coeval[A])(f: (Throwable) => Coeval[A]): Coeval[A] =
      fa.onErrorHandleWith(f)
    override def onErrorRecover[A](fa: Coeval[A])(pf: PartialFunction[Throwable, A]): Coeval[A] =
      fa.onErrorRecover(pf)
    override def onErrorRecoverWith[A](fa: Coeval[A])(pf: PartialFunction[Throwable, Coeval[A]]): Coeval[A] =
      fa.onErrorRecoverWith(pf)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy