scalaz.ioeffect.IO.scala Maven / Gradle / Ivy
// Copyright (C) 2017-2018 John A. De Goes. All rights reserved.
package scalaz.ioeffect
import scala.annotation.switch
import scala.concurrent.duration._
import scalaz.{ -\/, @@, \/, \/-, unused, Maybe, Monad }
import scalaz.syntax.either._
import scalaz.ioeffect.Errors._
import scalaz.Liskov.<~<
import scalaz.Tags.Parallel
import scala.concurrent.{ ExecutionContext, Future }
/**
* An `IO[E, A]` ("Eye-Oh of Eeh Aye") is an immutable data structure that
* describes an effectful action that may fail with an `E`, run forever, or
* produce a single `A` at some point in the future.
*
* Conceptually, this structure is equivalent to `EitherT[F, E, A]` for some
* infallible effect monad `F`, but because monad transformers perform poorly
* in Scala, this structure bakes in the `EitherT` without runtime overhead.
*
* `IO` values are ordinary immutable values, and may be used like any other
* values in purely functional code. Because `IO` values just *describe*
* effects, which must be interpreted by a separate runtime system, they are
* entirely pure and do not violate referential transparency.
*
* `IO` values can efficiently describe the following classes of effects:
*
* * **Pure Values** — `IO.point`
* * **Synchronous Effects** — `IO.sync`
* * **Asynchronous Effects** — `IO.async`
* * **Concurrent Effects** — `io.fork`
* * **Resource Effects** — `io.bracket`
*
* The concurrency model is based on *fibers*, a user-land lightweight thread,
* which permit cooperative multitasking, fine-grained interruption, and very
* high performance with large numbers of concurrently executing fibers.
*
* `IO` values compose with other `IO` values in a variety of ways to build
* complex, rich, interactive applications. See the methods on `IO` for more
* details about how to compose `IO` values.
*
* In order to integrate with Scala, `IO` values must be interpreted into the
* Scala runtime. This process of interpretation executes the effects described
* by a given immutable `IO` value. For more information on interpreting `IO`
* values, see the default interpreter in `RTS` or the safe main function in
* `SafeApp`.
*/
sealed abstract class IO[E, A] { self =>
/**
* Maps an `IO[E, A]` into an `IO[E, B]` by applying the specified `A => B` function
* to the output of this action. Repeated applications of `map`
* (`io.map(f1).map(f2)...map(f10000)`) are guaranteed stack safe to a depth
* of at least 10,000.
*/
final def map[B](f: A => B): IO[E, B] = (self.tag: @switch) match {
case IO.Tags.Point =>
val io = self.asInstanceOf[IO.Point[E, A]]
new IO.Point(() => f(io.value()))
case IO.Tags.Strict =>
val io = self.asInstanceOf[IO.Strict[E, A]]
new IO.Strict(f(io.value))
case IO.Tags.Fail => self.asInstanceOf[IO[E, B]]
case _ => new IO.FlatMap(self, (a: A) => new IO.Strict(f(a)))
}
/**
* Creates a composite action that represents this action followed by another
* one that may depend on the value produced by this one.
*
* {{{
* val parsed = readFile("foo.txt").flatMap(file => parseFile(file))
* }}}
*/
final def flatMap[B](f0: A => IO[E, B]): IO[E, B] = new IO.FlatMap(self, f0)
/**
* Forks this action into its own separate fiber, returning immediately
* without the value produced by this action.
*
* The `Fiber[E, A]` returned by this action can be used to interrupt the
* forked fiber with some exception, or to join the fiber to "await" its
* computed value.
*
* {{{
* for {
* fiber <- subtask.fork
* // Do stuff...
* a <- subtask.join
* } yield a
* }}}
*/
final def fork[E2]: IO[E2, Fiber[E, A]] = new IO.Fork(this, Maybe.empty)
/**
* A more powerful version of `fork` that allows specifying a handler to be
* invoked on any exceptions that are not handled by the forked fiber.
*/
final def fork0[E2](
handler: Throwable => IO[Void, Unit]
): IO[E2, Fiber[E, A]] =
new IO.Fork(this, Maybe.just(handler))
/**
* Executes both this action and the specified action in parallel,
* returning a tuple of their results. If either individual action fails,
* then the returned action will fail.
*
* TODO: Replace with optimized primitive.
*/
final def par[B](that: IO[E, B]): IO[E, (A, B)] =
self
.attempt[E]
.raceWith(that.attempt[E])(
{
case (-\/(e), fiberb) =>
fiberb.interrupt(TerminatedException(e)) *> IO.fail(e)
case (\/-(a), fiberb) => IO.absolve(fiberb.join).map((b: B) => (a, b))
}, {
case (-\/(e), fibera) =>
fibera.interrupt(TerminatedException(e)) *> IO.fail(e)
case (\/-(b), fibera) => IO.absolve(fibera.join).map((a: A) => (a, b))
}
)
/**
* Races this action with the specified action, returning the first
* result to produce an `A`, whichever it is. If neither action succeeds,
* then the action will be terminated with some error.
*/
final def race(that: IO[E, A]): IO[E, A] =
raceWith(that)(
(a, fiber) => fiber.interrupt(LostRace(\/-(fiber))).const(a),
(a, fiber) => fiber.interrupt(LostRace(-\/(fiber))).const(a)
)
/**
* Races this action with the specified action, invoking the
* specified finisher as soon as one value or the other has been computed.
*/
final def raceWith[B, C](that: IO[E, B])(
finishLeft: (A, Fiber[E, B]) => IO[E, C],
finishRight: (B, Fiber[E, A]) => IO[E, C]
): IO[E, C] =
new IO.Race[E, A, B, C](self, that, finishLeft, finishRight)
/**
* Executes this action and returns its value, if it succeeds, but
* otherwise executes the specified action.
*/
final def orElse(that: => IO[E, A]): IO[E, A] =
self.attempt.flatMap(_.fold(_ => that, IO.now))
/**
* Maps over the error type. This can be used to lift a "smaller" error into
* a "larger" error.
*/
final def leftMap[E2](f: E => E2): IO[E2, A] =
attempt[E2].flatMap {
case -\/(e) => IO.fail[E2, A](f(e))
case \/-(a) => IO.now[E2, A](a)
}
/**
* Widens the error type to any supertype. While `leftMap` suffices for this
* purpose, this method is significantly faster for this purpose.
*/
final def widenError[E2](implicit @unused ev: E <~< E2): IO[E2, A] =
self.asInstanceOf[IO[E2, A]]
/**
* Widens the type to any supertype more efficiently than `map(identity)`.
*/
final def widen[A2](implicit @unused ev: A <~< A2): IO[E, A2] =
self.asInstanceOf[IO[E, A2]]
/**
* Executes this action, capturing both failure and success and returning
* the result in a `Disjunction`. This method is useful for recovering from
* `IO` actions that may fail.
*
* The error parameter of the returned `IO` may be chosen arbitrarily, since
* it is guaranteed the `IO` action does not raise any errors.
*/
final def attempt[E2]: IO[E2, E \/ A] = (self.tag: @switch) match {
case IO.Tags.Point =>
val io = self.asInstanceOf[IO.Point[E, A]]
new IO.Point(() => \/-(io.value()))
case IO.Tags.Strict =>
val io = self.asInstanceOf[IO.Strict[E, A]]
new IO.Strict(\/-(io.value))
case IO.Tags.SyncEffect =>
val io = self.asInstanceOf[IO.SyncEffect[E, A]]
new IO.SyncEffect(() => \/-(io.effect()))
case IO.Tags.Fail =>
val io = self.asInstanceOf[IO.Fail[E, A]]
new IO.Strict(-\/(io.error))
case _ => new IO.Attempt(self)
}
/**
* Ignores the error and value of this IO, useful for explicitly acknowledging
* that a cleanup task will have its result ignored.
*/
def ignore: IO[Void, Unit] = attempt[Void].toUnit
/**
* When this action represents acquisition of a resource (for example,
* opening a file, launching a thread, etc.), `bracket` can be used to ensure
* the acquisition is not interrupted and the resource is released.
*
* The function does two things:
*
* 1. Ensures this action, which acquires the resource, will not be
* interrupted. Of course, acquisition may fail for internal reasons (an
* uncaught exception).
* 2. Ensures the `release` action will not be interrupted, and will be
* executed so long as this action successfully acquires the resource.
*
* In between acquisition and release of the resource, the `use` action is
* executed.
*
* If the `release` action fails, then the entire action will fail even
* if the `use` action succeeds. If this fail-fast behavior is not desired,
* errors produced by the `release` action can be caught and ignored.
*
* {{{
* openFile("data.json").bracket(closeFile) { file =>
* for {
* header <- readHeader(file)
* ...
* } yield result
* }
* }}}
*/
final def bracket[B](
release: A => IO[Void, Unit]
)(use: A => IO[E, B]): IO[E, B] =
new IO.Bracket(this, (_: ExitResult[E, B], a: A) => release(a), use)
/**
* A more powerful version of `bracket` that provides information on whether
* or not `use` succeeded to the release action.
*/
final def bracket0[B](
release: (ExitResult[E, B], A) => IO[Void, Unit]
)(use: A => IO[E, B]): IO[E, B] =
new IO.Bracket(this, release, use)
/**
* A less powerful variant of `bracket` where the value produced by this
* action is not needed.
*/
final def bracket_[B](release: IO[Void, Unit])(use: IO[E, B]): IO[E, B] =
self.bracket(_ => release)(_ => use)
/**
* Executes the specified finalizer, whether this action succeeds, fails, or
* is interrupted.
*/
final def ensuring(finalizer: IO[Void, Unit]): IO[E, A] =
IO.unit.bracket(_ => finalizer)(_ => self)
/**
* Executes the release action only if there was an error.
*/
final def bracketOnError[B](
release: A => IO[Void, Unit]
)(use: A => IO[E, B]): IO[E, B] =
bracket0(
(r: ExitResult[E, B], a: A) =>
r match {
case ExitResult.Failed(_) => release(a)
case ExitResult.Terminated(_) => release(a)
case _ => IO.unit
}
)(use)
/**
* Runs the specified cleanup action if this action errors, providing the
* error to the cleanup action. The cleanup action will not be interrupted.
*/
final def onError(cleanup: Throwable \/ E => IO[Void, Unit]): IO[E, A] =
IO.unit[E]
.bracket0(
(r: ExitResult[E, A], a: Unit) =>
r match {
case ExitResult.Failed(e) => cleanup(e.right)
case ExitResult.Terminated(e) => cleanup(e.left)
case _ => IO.unit
}
)(_ => self)
/**
* Supervises this action, which ensures that any fibers that are forked by
* the action are interrupted with the specified error when this action
* completes.
*/
final def supervised(error: Throwable): IO[E, A] = new IO.Supervise(self, error)
/**
* Performs this action non-interruptibly. This will prevent the action from
* being terminated externally, but the action may fail for internal reasons
* (e.g. an uncaught error) or terminate due to defect.
*/
final def uninterruptibly: IO[E, A] = new IO.Uninterruptible(self)
/**
* Recovers from all errors.
*
* {{{
* openFile("config.json").catchAll(_ => IO.now(defaultConfig))
* }}}
*/
final def catchAll[E2](h: E => IO[E2, A]): IO[E2, A] =
self.attempt[E2].flatMap {
case -\/(e) => h(e)
case \/-(a) => IO.now[E2, A](a)
}
/**
* Recovers from some or all of the error cases.
*
* {{{
* openFile("data.json").catchSome {
* case FileNotFoundException(_) => openFile("backup.json")
* }
* }}}
*/
final def catchSome(pf: PartialFunction[E, IO[E, A]]): IO[E, A] = {
def tryRescue(t: E): IO[E, A] =
if (pf.isDefinedAt(t)) pf(t) else IO.fail(t)
self.attempt[E].flatMap(_.fold(tryRescue, IO.now))
}
/**
* Maps this action to the specified constant while preserving the
* effects of this action.
*/
final def const[B](b: => B): IO[E, B] = self.map(_ => b)
/**
* A variant of `flatMap` that ignores the value produced by this action.
*/
final def *>[B](io: => IO[E, B]): IO[E, B] = self.flatMap(_ => io)
/**
* Sequences the specified action after this action, but ignores the
* value produced by the action.
*/
final def <*[B](io: => IO[E, B]): IO[E, A] = self.flatMap(io.const(_))
/**
* Sequentially zips this effect with the specified effect using the
* specified combiner function.
*/
final def zipWith[B, C](that: IO[E, B])(f: (A, B) => C): IO[E, C] =
self.flatMap(a => that.map(b => f(a, b)))
/**
* Repeats this action forever (until the first error).
*/
final def forever[B]: IO[E, B] = self *> self.forever
/**
* Retries continuously until this action succeeds.
*/
final def retry: IO[E, A] = self.orElse(retry)
/**
* Retries this action the specified number of times, until the first success.
* Note that the action will always be run at least once, even if `n < 1`.
*/
final def retryN(n: Int): IO[E, A] =
retryBackoff(n, 1.0, Duration.fromNanos(0))
/**
* Retries continuously until the action succeeds or the specified duration
* elapses.
*/
final def retryFor(duration: Duration): IO[E, Maybe[A]] =
retry
.map(Maybe.just[A])
.race(IO.sleep[E](duration) *> IO.now[E, Maybe[A]](Maybe.empty[A]))
/**
* Retries continuously, increasing the duration between retries each time by
* the specified multiplication factor, and stopping after the specified upper
* limit on retries.
*/
final def retryBackoff(n: Int, factor: Double, duration: Duration): IO[E, A] =
if (n <= 1) self
else
self.orElse(
IO.sleep(duration) *> retryBackoff(n - 1, factor, duration * factor)
)
/**
* Repeats this action continuously until the first error, with the specified
* interval between each full execution.
*/
final def repeat[B](interval: Duration): IO[E, B] =
self *> IO.sleep(interval) *> repeat(interval)
/**
* Repeats this action continuously until the first error, with the specified
* interval between the start of each full execution. Note that if the
* execution of this action takes longer than the specified interval, then the
* action will instead execute as quickly as possible, but not
* necessarily at the specified interval.
*/
final def repeatFixed[B](interval: Duration): IO[E, B] =
repeatFixed0(IO.sync(System.nanoTime()))(interval)
final def repeatFixed0[B](
nanoTime: IO[Void, Long]
)(interval: Duration): IO[E, B] =
IO.flatten(nanoTime[E].flatMap { start =>
val gapNs = interval.toNanos
def tick[C](n: Int): IO[E, C] =
self *> nanoTime[E].flatMap { now =>
val await = ((start + n * gapNs) - now).max(0L)
IO.sleep(await.nanoseconds) *> tick(n + 1)
}
tick(1)
})
/**
* Maps this action to one producing unit, but preserving the effects of
* this action.
*/
final def toUnit: IO[E, Unit] = const(())
/**
* Calls the provided function with the result of this action, and
* sequences the resulting action after this action, but ignores the
* value produced by the action.
*
* {{{
* readFile("data.json").peek(putStrLn)
* }}}
*/
final def peek[B](f: A => IO[E, B]): IO[E, A] =
self.flatMap(a => f(a).const(a))
/**
* Times out this action by the specified duration.
*
* {{{
* action.timeout(1.second)
* }}}
*/
final def timeout(duration: Duration): IO[E, Maybe[A]] = {
val timer = IO.now[E, Maybe[A]](Maybe.empty[A])
self.map(Maybe.just[A]).race(timer.delay(duration))
}
/**
* Returns a new action that executes this one and times the execution.
*/
final def timed: IO[E, (Duration, A)] = timed0(IO.sync(System.nanoTime()))
/**
* A more powerful variation of `timed` that allows specifying the clock.
*/
final def timed0(nanoTime: IO[E, Long]): IO[E, (Duration, A)] =
summarized[Long, Duration]((start, end) => Duration.fromNanos(end - start))(
nanoTime
)
/**
* Summarizes a action by computing some value before and after execution, and
* then combining the values to produce a summary, together with the result of
* execution.
*/
final def summarized[B, C](f: (B, B) => C)(summary: IO[E, B]): IO[E, (C, A)] =
for {
start <- summary
value <- self
end <- summary
} yield (f(start, end), value)
/**
* Delays this action by the specified amount of time.
*/
final def delay(duration: Duration): IO[E, A] =
IO.sleep(duration) *> self
/**
* Runs this action in a new fiber, resuming when the fiber terminates.
*/
final def run[E2]: IO[E2, ExitResult[E, A]] = new IO.Run(self)
/**
* Lets the user define separate continuations for the case of failure (`err`) or
* success (`succ`). Executes this action and based on the result executes
* the next action, `err` or `succ`.
*/
final def redeem[E2, B](err: E => IO[E2, B], succ: A => IO[E2, B]): IO[E2, B] =
self.attempt[E2].flatMap(_.fold(err, succ))
/**
* Fold errors and values to some `B`, resuming with `IO[Void, B]` -
* a slightly less powerful version of `redeem`.
*/
final def fold[E2, B](err: E => B, succ: A => B): IO[E2, B] =
self.attempt[E2].map(_.fold(err, succ))
/**
* For some monad F and some error type E, lift this IO
* into F if there is a monadIO instance for F
*/
final def liftIO[F[_]: Monad](implicit M: MonadIO[F, E]): F[A] = M.liftIO(self)
/**
* An integer that identifies the term in the `IO` sum type to which this
* instance belongs (e.g. `IO.Tags.Point`).
*/
def tag: Int
}
object IO extends IOInstances {
type Par[e, a] = IO[e, a] @@ Parallel
final object Tags {
final val FlatMap = 0
final val Point = 1
final val Strict = 2
final val SyncEffect = 3
final val Fail = 4
final val AsyncEffect = 5
final val AsyncIOEffect = 6
final val Attempt = 7
final val Fork = 8
final val Race = 9
final val Suspend = 10
final val Bracket = 11
final val Uninterruptible = 12
final val Sleep = 13
final val Supervise = 14
final val Terminate = 15
final val Supervisor = 16
final val Run = 17
}
final class FlatMap[E, A0, A] private[IO] (val io: IO[E, A0], val flatMapper: A0 => IO[E, A]) extends IO[E, A] {
override def tag: Int = Tags.FlatMap
}
final class Point[E, A] private[IO] (val value: () => A) extends IO[E, A] {
override def tag: Int = Tags.Point
}
final class Strict[E, A] private[IO] (val value: A) extends IO[E, A] {
override def tag: Int = Tags.Strict
}
final class SyncEffect[E, A] private[IO] (val effect: () => A) extends IO[E, A] {
override def tag: Int = Tags.SyncEffect
}
final class Fail[E, A] private[IO] (val error: E) extends IO[E, A] {
override def tag: Int = Tags.Fail
}
final class AsyncEffect[E, A] private[IO] (val register: (ExitResult[E, A] => Unit) => Async[E, A]) extends IO[E, A] {
override def tag: Int = Tags.AsyncEffect
}
final class AsyncIOEffect[E, A] private[IO] (val register: (ExitResult[E, A] => Unit) => IO[E, Unit])
extends IO[E, A] {
override def tag: Int = Tags.AsyncIOEffect
}
final class Attempt[E1, E2, A] private[IO] (val value: IO[E1, A]) extends IO[E2, E1 \/ A] {
override def tag: Int = Tags.Attempt
}
final class Fork[E1, E2, A] private[IO] (val value: IO[E1, A], val handler: Maybe[Throwable => IO[Void, Unit]])
extends IO[E2, Fiber[E1, A]] {
override def tag: Int = Tags.Fork
}
final class Race[E, A0, A1, A] private[IO] (
val left: IO[E, A0],
val right: IO[E, A1],
val finishLeft: (A0, Fiber[E, A1]) => IO[E, A],
val finishRight: (A1, Fiber[E, A0]) => IO[E, A]
) extends IO[E, A] {
override def tag: Int = Tags.Race
}
final class Suspend[E, A] private[IO] (val value: () => IO[E, A]) extends IO[E, A] {
override def tag: Int = Tags.Suspend
}
final class Bracket[E, A, B] private[IO] (
val acquire: IO[E, A],
val release: (ExitResult[E, B], A) => IO[Void, Unit],
val use: A => IO[E, B]
) extends IO[E, B] {
override def tag: Int = Tags.Bracket
}
final class Uninterruptible[E, A] private[IO] (val io: IO[E, A]) extends IO[E, A] {
override def tag: Int = Tags.Uninterruptible
}
final class Sleep[E] private[IO] (val duration: Duration) extends IO[E, Unit] {
override def tag: Int = Tags.Sleep
}
final class Supervise[E, A] private[IO] (val value: IO[E, A], val error: Throwable) extends IO[E, A] {
override def tag: Int = Tags.Supervise
}
final class Terminate[E, A] private[IO] (val cause: Throwable) extends IO[E, A] {
override def tag: Int = Tags.Terminate
}
final class Supervisor[E] private[IO] () extends IO[E, Throwable => IO[Void, Unit]] {
override def tag: Int = Tags.Supervisor
}
final class Run[E1, E2, A] private[IO] (val value: IO[E1, A]) extends IO[E2, ExitResult[E1, A]] {
override def tag: Int = Tags.Run
}
/**
* Lifts a strictly evaluated value into the `IO` monad.
*/
final def now[E, A](a: A): IO[E, A] = new Strict(a)
/**
* Lifts a non-strictly evaluated value into the `IO` monad. Do not use this
* function to capture effectful code. The result is undefined but may
* include duplicated effects.
*/
final def point[E, A](a: => A): IO[E, A] = new Point(() => a)
/**
* Creates an `IO` value that represents failure with the specified error.
* The moral equivalent of `throw` for pure code.
*/
final def fail[E, A](error: E): IO[E, A] = new Fail(error)
/**
* Strictly-evaluated unit lifted into the `IO` monad.
*/
final def unit[E]: IO[E, Unit] = Unit.asInstanceOf[IO[E, Unit]]
/**
* Sleeps for the specified duration. This is always asynchronous.
*/
final def sleep[E](duration: Duration): IO[E, Unit] = new Sleep(duration)
/**
* Supervises the specified action, which ensures that any actions directly
* forked by the action are killed with the specified error upon the action's
* own termination.
*/
final def supervise[E, A](io: IO[E, A], error: Throwable): IO[E, A] = new Supervise(io, error)
/**
* Flattens a nested action.
*/
final def flatten[E, A](io: IO[E, IO[E, A]]): IO[E, A] = io.flatMap(a => a)
/**
* Lazily produces an `IO` value whose construction may have actional costs
* that should be be deferred until evaluation.
*
* Do not use this method to effectfully construct `IO` values. The results
* will be undefined and most likely involve the physical explosion of your
* computer in a heap of rubble.
*/
final def suspend[E, A](io: => IO[E, A]): IO[E, A] = new Suspend(() => io)
/**
* Terminates the fiber executing this action, running all finalizers.
*/
final def terminate[E, A](t: Throwable): IO[E, A] = new Terminate(t)
/**
* Imports a synchronous effect into a pure `IO` value.
*
* {{{
* val nanoTime: IO[Void, Long] = IO.sync(System.nanoTime())
* }}}
*/
final def sync[E, A](effect: => A): IO[E, A] = new SyncEffect(() => effect)
/**
*
* Imports a synchronous effect into a pure `IO` value, translating any
* throwables into a `Throwable` failure in the returned value.
*
* {{{
* def putStrLn(line: String): IO[Throwable, Unit] = IO.syncThrowable(println(line))
* }}}
*/
final def syncThrowable[A](effect: => A): IO[Throwable, A] =
IO.suspend {
try {
val a = effect
IO.sync(a)
} catch {
case t: Throwable => IO.fail(t)
}
}
/**
*
* Imports a synchronous effect into a pure `IO` value, translating any
* exceptions into an `Exception` failure in the returned value.
*
* {{{
* def putStrLn(line: String): IO[Exception, Unit] = IO.syncException(println(line))
* }}}
*/
final def syncException[A](effect: => A): IO[Exception, A] =
syncCatch(effect) {
case e: Exception => e
}
/**
* Safely imports an exception-throwing synchronous effect into a pure `IO`
* value, translating the specified throwables into `E` with the provided
* user-defined function.
*/
final def syncCatch[E, A](
effect: => A
)(f: PartialFunction[Throwable, E]): IO[E, A] =
IO.absolve(IO.sync(try {
val result = effect
result.right
} catch { case t: Throwable if f.isDefinedAt(t) => f(t).left }))
/**
* Imports an asynchronous effect into a pure `IO` value. See `async0` for
* the more expressive variant of this function.
*/
final def async[E, A](register: (ExitResult[E, A] => Unit) => Unit): IO[E, A] =
new AsyncEffect(callback => {
register(callback)
Async.later[E, A]
})
/**
* Imports an asynchronous effect into a pure `IO` value. This formulation is
* necessary when the effect is itself expressed in terms of `IO`.
*/
final def asyncPure[E, A](register: (ExitResult[E, A] => Unit) => IO[E, Unit]): IO[E, A] = new AsyncIOEffect(register)
/**
* Imports an asynchronous effect into a pure `IO` value. The effect has the
* option of returning the value synchronously, which is useful in cases
* where it cannot be determined if the effect is synchronous or asynchronous
* until the effect is actually executed. The effect also has the option of
* returning a canceler, which will be used by the runtime to cancel the
* asynchronous effect if the fiber executing the effect is interrupted.
*/
final def async0[E, A](register: (ExitResult[E, A] => Unit) => Async[E, A]): IO[E, A] = new AsyncEffect(register)
/**
* Returns a action that will never produce anything. The moral
* equivalent of `while(true) {}`, only without the wasted CPU cycles.
*/
final def never[E, A]: IO[E, A] = Never.asInstanceOf[IO[E, A]]
/**
* Submerges the error case of a disjunction into the `IO`. The inverse
* operation of `IO.attempt`.
*/
final def absolve[E, A](v: IO[E, E \/ A]): IO[E, A] =
v.flatMap {
case -\/(e) => IO.fail(e)
case \/-(a) => IO.now(a)
}
/**
* Retrieves the supervisor associated with the fiber running the action
* returned by this method.
*/
def supervisor[E]: IO[E, Throwable => IO[Void, Unit]] = new Supervisor()
/**
* Requires that the given `IO[E, Maybe[A]\]` contain a value. If there is no
* value, then the specified error will be raised.
*/
final def require[E, A](error: E): IO[E, Maybe[A]] => IO[E, A] =
(io: IO[E, Maybe[A]]) => io.flatMap(_.cata(IO.now[E, A](_), IO.fail[E, A](error)))
/**
* Convert from Future (lifted into IO eagerly via `now`, or delayed via
* `point`) to a `Task`. Futures are inefficient and unsafe: this is provided
* only as a convenience for integrating with legacy systems.
*/
final def fromFuture[E, A](io: Task[Future[A]])(ec: ExecutionContext): Task[A] =
io.attempt.flatMap { f =>
IO.async { cb =>
f.fold(
t => cb(ExitResult.Failed(t)),
_.onComplete(
t =>
cb(t match {
case scala.util.Success(a) => ExitResult.Completed(a)
case scala.util.Failure(t) => ExitResult.Failed(t)
})
)(ec)
)
}
}
// TODO: Make this fast, generalize from `Unit` to `A: Semigroup`,
// and use `IList` instead of `List`.
def forkAll[E2](l: List[IO[E2, Unit]]): IO[E2, Unit] = l match {
case Nil => IO.unit[E2]
case x :: xs => x.fork.toUnit *> forkAll(xs)
}
private final val Never: IO[Nothing, Any] =
IO.async[Nothing, Any] { (k: (ExitResult[Nothing, Any]) => Unit) =>
}
private final val Unit: IO[Nothing, Unit] = now(())
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy