A monad that can describe asynchronous or synchronous computations that produce exactly one result.
Type class for Async data types that are cancelable and can be started concurrently.
Type class for Async data types that are cancelable and can be started concurrently.
Thus this type class allows abstracting over data types that:
Due to these restrictions, this type class also affords to describe
a start operation that can start async
processing, suspended in the context of F[_]
and that can be
cancelled or joined.
Without cancelation being baked in, we couldn't afford to do it. See below.
The signature exposed by the cancelable builder is this:
(Either[Throwable, A] => Unit) => F[Unit]
F[Unit]
is used to represent a cancelation action which will
send a signal to the producer, that may observe it and cancel the
asynchronous process.
Simple asynchronous processes, like Scala's Future
, can be
described with this very basic and side-effectful type and you
should recognize what is more or less the signature of
Future#onComplete
or of Async.async (minus the error
handling):
(A => Unit) => Unit
But many times the abstractions built to deal with asynchronous tasks can also provide a way to cancel such processes, to be used in race conditions in order to cleanup resources early, so a very basic and side-effectful definition of asynchronous processes that can be cancelled would be:
(A => Unit) => Cancelable
This is approximately the signature of JavaScript's setTimeout
,
which will return a "task ID" that can be used to cancel it. Or of
Java's ScheduledExecutorService#schedule
, which will return a
Java ScheduledFuture
that has a .cancel()
operation on it.
Similarly, for Concurrent
data types, we can provide
cancelation logic, that can be triggered in race conditions to
cancel the on-going processing, only that Concurrent
's
cancelable token is an action suspended in an IO[Unit]
. See
IO.cancelable.
Suppose you want to describe a "sleep" operation, like that described
by Timer to mirror Java's ScheduledExecutorService.schedule
or JavaScript's setTimeout
:
def sleep(d: FiniteDuration): F[Unit]
This signature is in fact incomplete for data types that are not cancelable, because such equivalent operations always return some cancelation token that can be used to trigger a forceful interruption of the timer. This is not a normal "dispose" or "finally" clause in a try/catch block, because "cancel" in the context of an asynchronous process is concurrent with the task's own run-loop.
To understand what this means, consider that in the case of our
sleep
as described above, on cancelation we'd need a way to
signal to the underlying ScheduledExecutorService
to forcefully
remove the scheduled Runnable
from its internal queue of
scheduled tasks, before its execution. Therefore, without a
cancelable data type, a safe signature needs to return a
cancelation token, so it would look like this:
def sleep(d: FiniteDuration): F[(F[Unit], F[Unit])]
This function is returning a tuple, with one F[Unit]
to wait for
the completion of our sleep and a second F[Unit]
to cancel the
scheduled computation in case we need it. This is in fact the shape
of Fiber's API. And this is exactly what the
start operation returns.
The difference between a Concurrent data type and one that
is only Async is that you can go from any F[A]
to a
F[Fiber[F, A]]
, to participate in race conditions and that can be
cancelled should the need arise, in order to trigger an early
release of allocated resources.
Thus a Concurrent data type can safely participate in race
conditions, whereas a data type that is only Async cannot do it
without exposing and forcing the user to work with cancelation
tokens. An Async data type cannot expose for example a start
operation that is safe.
Type class describing effect data types that are cancelable and can be evaluated concurrently.
Type class describing effect data types that are cancelable and can be evaluated concurrently.
In addition to the algebras of Concurrent and of
Effect, instances must also implement a
runCancelable operation that
triggers the evaluation, suspended in the IO
context, but that
also returns a token that can be used for cancelling the running
computation.
Note this is the safe and generic version of IO.unsafeRunCancelable.
A monad that can suspend side effects into the F
context and
that supports lazy and potentially asynchronous evaluation.
A monad that can suspend side effects into the F
context and
that supports lazy and potentially asynchronous evaluation.
This type class is describing data types that:
Note this is the safe and generic version of IO.unsafeRunAsync
(aka Haskell's unsafePerformIO
).
Fiber
represents the (pure) result of an Async data type (e.g.
Fiber
represents the (pure) result of an Async data type (e.g. IO)
being started concurrently and that can be either joined or cancelled.
You can think of fibers as being lightweight threads, a fiber being a concurrency primitive for doing cooperative multi-tasking.
For example a Fiber
value is the result of evaluating IO.start:
val io = IO.shift *> IO(println("Hello!")) val fiber: IO[Fiber[IO, Unit]] = io.start
Usage example:
for { fiber <- IO.shift *> launchMissiles.start _ <- runToBunker.handleErrorWith { error => // Retreat failed, cancel launch (maybe we should // have retreated to our bunker before the launch?) fiber.cancel *> IO.raiseError(error) } aftermath <- fiber.join } yield { aftermath }
A pure abstraction representing the intention to perform a side effect, where the result of that side effect may be obtained synchronously (via return) or asynchronously (via callback).
A pure abstraction representing the intention to perform a side effect, where the result of that side effect may be obtained synchronously (via return) or asynchronously (via callback).
Effects contained within this abstraction are not evaluated until the "end of the world", which is to say, when one of the "unsafe" methods are used. Effectful results are not memoized, meaning that memory overhead is minimal (and no leaks), and also that a single effect may be run multiple times in a referentially-transparent manner. For example:
val ioa = IO { println("hey!") } val program = for { _ <- ioa _ <- ioa } yield () program.unsafeRunSync()
The above will print "hey!" twice, as the effect will be re-run each time it is sequenced in the monadic chain.
IO
is trampolined for all synchronous joins. This means that
you can safely call flatMap
in a recursive function of arbitrary
depth, without fear of blowing the stack. However, IO
cannot
guarantee stack-safety in the presence of arbitrarily nested
asynchronous suspensions. This is quite simply because it is
impossible (on the JVM) to guarantee stack-safety in that case.
For example:
def lie[A]: IO[A] = IO.async(cb => cb(Right(lie))).flatMap(a => a)
This should blow the stack when evaluated. Also note that there is
no way to encode this using tailRecM
in such a way that it does
not blow the stack. Thus, the tailRecM
on Monad[IO]
is not
guaranteed to produce an IO
which is stack-safe when run, but
will rather make every attempt to do so barring pathological
structure.
IO
makes no attempt to control finalization or guaranteed
resource-safety in the presence of concurrent preemption, simply
because IO
does not care about concurrent preemption at all!
IO
actions are not interruptible and should be considered
broadly-speaking atomic, at least when used purely.
A monad that can suspend the execution of side effects
in the F[_]
context.
Timer is a scheduler of tasks.
Timer is a scheduler of tasks.
This is the purely functional equivalent of:
It provides:
It does all of that in an F
monadic context that can suspend
side effects and is capable of asynchronous execution (e.g. IO).
This is NOT a type class, as it does not have the coherence requirement.
A monad that can describe asynchronous or synchronous computations that produce exactly one result.
On Asynchrony
An asynchronous task represents logic that executes independent of the main program flow, or current callstack. It can be a task whose result gets computed on another thread, or on some other machine on the network.
In terms of types, normally asynchronous processes are represented as:
This signature can be recognized in the "Observer pattern" described in the "Gang of Four", although it should be noted that without an
onComplete
event (like in the Rx Observable pattern) you can't detect completion in case this callback can be called zero or multiple times.Some abstractions allow for signaling an error condition (e.g.
MonadError
data types), so this would be a signature that's closer to Scala'sFuture#onComplete
:And many times the abstractions built to deal with asynchronous tasks also provide a way to cancel such processes, to be used in race conditions in order to cleanup resources early:
This is approximately the signature of JavaScript's
setTimeout
, which will return a "task ID" that can be used to cancel it.N.B. this type class in particular is NOT describing cancelable async processes, see the Concurrent type class for that.
Async Type class
This type class allows the modeling of data types that:
N.B. on the "one result" signaling, this is not an exactly once requirement. At this point streaming types can implement
Async
and such an exactly once requirement is only clear in Effect.Therefore the signature exposed by the async builder is this:
N.B. such asynchronous processes are not cancelable. See the Concurrent alternative for that.