The entry point for a purely-functional application on the JVM.
The entry point for a purely-functional application on the JVM.
import java.io.IOException import scalaz.zio.{App, IO} import scalaz.zio.console._ object MyApp extends App { def run(args: List[String]): IO[Nothing, ExitStatus] = myAppLogic.attempt.map(_.fold(_ => 1, _ => 0)).map(ExitStatus.ExitNow(_)) def myAppLogic: IO[IOException, Unit] = for { _ <- putStrLn("Hello! What is your name?") n <- getStrLn _ <- putStrLn("Hello, " + n + ", good to meet you!") } yield () }
The Async
class describes the return value of an asynchronous effect
that is imported into an IO
value.
The Async
class describes the return value of an asynchronous effect
that is imported into an IO
value.
Asynchronous effects can return later
, which represents an uninterruptible
asynchronous action, now
which represents a synchronously computed value,
maybeLater
, which represents an interruptible asynchronous action or maybeLaterIO
which represents an interruptible asynchronous action where the canceler has the
form Throwable => IO[Nothing, Unit]
A description of the result of executing an IO
value.
A description of the result of executing an IO
value. The result is either
completed with a value, failed because of an uncaught E
, or terminated
due to interruption or runtime error.
A fiber is a lightweight thread of execution that never consumes more than a whole thread (but may consume much less, depending on contention).
A fiber is a lightweight thread of execution that never consumes more than a
whole thread (but may consume much less, depending on contention). Fibers are
spawned by forking IO
actions, which, conceptually at least, runs them
concurrently with the parent IO
action.
Fibers can be joined, yielding their result other fibers, or interrupted, which terminates the fiber with a runtime error.
Fork-Join Identity: fork >=> join = id
for { fiber1 <- io1.fork fiber2 <- io2.fork _ <- fiber1.interrupt(e) a <- fiber2.join } yield a
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.
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
App
.
A KleisliIO[E, A, B]
is an effectful function from A
to B
, which might
fail with an E
.
A KleisliIO[E, A, B]
is an effectful function from A
to B
, which might
fail with an E
.
This is the moral equivalent of A => IO[E, B]
, and, indeed, KleisliIO
extends this function type, and can be used in the same way.
The main advantage to using KleisliIO
is that it provides you a means of
importing an impure function A => B
into KleisliIO[E, A, B]
, without
actually wrapping the result of the function in an IO
value.
This allows the implementation to aggressively fuse operations on impure
functions, which in turn can result in significantly higher-performance and
far less heap utilization than equivalent approaches modeled with IO
.
The implementation allows you to lift functions from A => IO[E, B]
into a
KleisliIO[E, A, B]
. Such functions cannot be optimized, but will be handled
correctly and can work in conjunction with optimized (fused) KleisliIO
.
Those interested in learning more about modeling effects with KleisliIO
are
encouraged to read John Hughes paper on the subject: Generalizing Monads to
Arrows (www.cse.chalmers.se/~rjmh/Papers/arrows.pdf). The implementation in
this file contains many of the same combinators as Hughes implementation.
A word of warning: while even very complex code can be expressed in
KleisliIO
, there is a point of diminishing return. If you find yourself
using deeply nested tuples to propagate information forward, it may be no
faster than using IO
.
Given the following two KleisliIO
:
val readLine = KleisliIO.impureVoid((_ : Unit) => scala.Console.readLine()) val printLine = KleisliIO.impureVoid((line: String) => println(line))
Then the following two programs are equivalent:
// Program 1 val program1: IO[Nothing, Unit] = for { name <- getStrLn _ <- putStrLn("Hello, " + name) } yield ()) // Program 2 val program2: IO[Nothing, Unit] = (readLine >>> KleisliIO.lift("Hello, " + _) >>> printLine)(())
Similarly, the following two programs are equivalent:
// Program 1 val program1: IO[Nothing, Unit] = for { line1 <- getStrLn line2 <- getStrLn _ <- putStrLn("You wrote: " + line1 + ", " + line2) } yield ()) // Program 2 val program2: IO[Nothing, Unit] = (readLine.zipWith(readLine)("You wrote: " + _ + ", " + _) >>> printLine)(())
In both of these examples, the KleisliIO
program is faster because it is
able to perform fusion of effectful functions.
A promise represents an asynchronous variable that can be set exactly once,
with the ability for an arbitrary number of fibers to suspend (by calling
get
) and automatically resume when the variable is set.
A promise represents an asynchronous variable that can be set exactly once,
with the ability for an arbitrary number of fibers to suspend (by calling
get
) and automatically resume when the variable is set.
Promises can be used for building primitive actions whose completions require the coordinated action of multiple fibers, and for building higher-level concurrent or asynchronous structures.
for { promise <- Promise.make[Nothing, Int] _ <- promise.complete(42).delay(1.second).fork value <- promise.get // Resumes when forked fiber completes promise } yield value
* A Queue
is a lightweight, asynchronous queue.
* A Queue
is a lightweight, asynchronous queue. This implementation is
* naive, if functional, and could benefit from significant optimization.
*
* TODO:
*
* 1. Investigate using a faster option than Queue
, because Queue
has
* O(n)
length
method.
* 2. Benchmark to see how slow this implementation is and if there are any
* easy ways to improve performance.
This trait provides a high-performance implementation of a runtime system for
the IO
monad on the JVM.
A mutable atomic reference for the IO
monad.
A mutable atomic reference for the IO
monad. This is the IO
equivalent of
a volatile var
, augmented with atomic operations, which make it useful as a
reasonably efficient (if low-level) concurrency primitive.
for { ref <- Ref(2) v <- ref.modify(_ + 3) _ <- putStrLn("Value = " + v.debug) // Value = 5 } yield ()
Defines a stateful, possibly effectful, recurring schedule of actions.
Defines a stateful, possibly effectful, recurring schedule of actions.
A Schedule[A, B]
consumes A
values, and based on the inputs and the
internal state, decides whether to recur or conclude. Every decision is
accompanied by a (possibly zero) delay, and an output value of type B
.
Schedules compose in each of the following ways:
1. Intersection, using the &&
operator, which requires that both schedules
continue, using the longer of the two durations.
2. Union, using the ||
operator, which requires that only one schedule
continues, using the shorter of the two durations.
3. Sequence, using the <||>
operator, which runs the first schedule until
it ends, and then switches over to the second schedule.
Thanks to (1), Schedule[A, B]
forms an applicative functor on the output
value B
, allowing rich composition of different schedules.