cats.effect
package cats.effect
Type members
Classlikes
@implicitNotFound("Could not find an instance of Async for ${F}")
A monad that can describe asynchronous or synchronous computations
that produce exactly one result.
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.
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:
{{{
(A => Unit) => Unit
}}}
{{{
(A => Unit) => Unit
}}}
This signature can be recognized in the "Observer pattern" described
in the "Gang of Four", although it should be noted that without
an
detect completion in case this callback can be called zero or
multiple times.
in the "Gang of Four", although it should be noted that without
an
onComplete
event (like in the Rx Observable pattern) you can'tdetect completion in case this callback can be called zero or
multiple times.
Some abstractions allow for signaling an error condition
(e.g.
that's closer to Scala's
(e.g.
MonadError
data types), so this would be a signaturethat's closer to Scala's
Future#onComplete
:{{{
(Either[Throwable, A] => Unit) => Unit
}}}
(Either[Throwable, A] => Unit) => Unit
}}}
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:
also provide a way to cancel such processes, to be used in race
conditions in order to cleanup resources early:
{{{
(A => Unit) => Cancelable
}}}
(A => Unit) => Cancelable
}}}
This is approximately the signature of JavaScript's
which will return a "task ID" that can be used to cancel it.
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 processes, see the Concurrent type class for that.
==Async Type class==
This type class allows the modeling of data types that:
-
can start asynchronous processes
-
can emit one result on completion
-
can end in error
N.B. on the "one result" signaling, this is not an ''exactly once''
requirement. At this point streaming types can implement
and such an ''exactly once'' requirement is only clear in Effect.
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:
builder is this:
{{{
(Either[Throwable, A] => Unit) => Unit
}}}
(Either[Throwable, A] => Unit) => Unit
}}}
N.B. such asynchronous processes are not cancelable.
See the Concurrent alternative for that.
See the Concurrent alternative for that.
- Companion
- object
An execution context that is safe to use for blocking operations.
Used in conjunction with ContextShift, this type allows us to write functions
that require a special
use of a shared, general purpose pool (e.g. the global context).
that require a special
ExecutionContext
for evaluation, while discouraging theuse of a shared, general purpose pool (e.g. the global context).
Instances of this class should NOT be passed implicitly because they hold state
and in some cases your application may need different instances of Blocker.
and in some cases your application may need different instances of Blocker.
- Companion
- object
An extension of
a generalized abstracted pattern of safe resource acquisition and
release in the face of errors or interruption.
MonadError
exposing the bracket
operation,a generalized abstracted pattern of safe resource acquisition and
release in the face of errors or interruption.
- Companion
- object
@implicitNotFound("Cannot find an implicit value for Clock[${F}]:\n* import an implicit Timer[${F}] in scope or\n* create a Clock[${F}] instance with Clock.create\n")
Clock provides the current time, as a pure alternative to:
-
Java's
System.currentTimeMillis
for getting the "real-time clock" and
System.nanoTime
for a monotonic clock useful for time measurements -
JavaScript's
Date.now()
andperformance.now()
This is NOT a type class, as it does not have the coherence
requirement.
requirement.
- Companion
- object
@implicitNotFound("Could not find an instance of Concurrent for ${F}")
Type class for Async data types that are cancelable and
can be started concurrently.
can be started concurrently.
Thus this type class allows abstracting over data types that:
-
implement the Async algebra, with all its restrictions
-
can provide logic for cancellation, to be used in race
conditions in order to release resources early
(in its cancelable builder)
Due to these restrictions, this type class also affords to describe
a start operation that can start async
processes, suspended in the context of
canceled or joined.
a start operation that can start async
processes, suspended in the context of
F[_]
and that can becanceled or joined.
Without cancellation being baked in, we couldn't afford to do it.
See below.
See below.
==Cancelable builder==
The signature exposed by the cancelable
builder is this:
builder is this:
{{{
(Either[Throwable, A] => Unit) => CancelToken[F]
}}}
(Either[Throwable, A] => Unit) => CancelToken[F]
}}}
CancelToken[F] is just an alias for
used to represent a cancellation action which will send a signal
to the producer, that may observe it and cancel the asynchronous
process.
F[Unit]
andused to represent a cancellation action which will send a signal
to the producer, that may observe it and cancel the asynchronous
process.
==On Cancellation==
Simple asynchronous processes, like Scala's
described with this very basic and side-effectful type and you
should recognize what is more or less the signature of
handling):
Future
, can bedescribed 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 errorhandling):
{{{
(A => Unit) => Unit
}}}
(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 canceled would be:
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 canceled would be:
{{{
(A => Unit) => CancelToken
}}}
(A => Unit) => CancelToken
}}}
This is approximately the signature of JavaScript's
which will return a "task ID" that can be used to cancel it. Or of
Java's
Java
setTimeout
,which will return a "task ID" that can be used to cancel it. Or of
Java's
ScheduledExecutorService#schedule
, which will return aJava
ScheduledFuture
that has a .cancel()
operation on it.Similarly, for
cancellation logic that can be triggered in race conditions to
cancel the on-going processing, only that
cancelation token is an action suspended in an
Concurrent
data types, we can providecancellation logic that can be triggered in race conditions to
cancel the on-going processing, only that
Concurrent
'scancelation token is an action suspended in an
F[Unit]
.Suppose you want to describe a "sleep" operation, like that described
by Timer to mirror Java's
or JavaScript's
by Timer to mirror Java's
ScheduledExecutorService.schedule
or JavaScript's
setTimeout
:{{{
def sleep(d: FiniteDuration): F[Unit]
}}}
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
cancellation 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.
cancelable, because such equivalent operations always return some
cancellation 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
signal to the underlying
remove the scheduled
scheduled tasks, ''before'' its execution. Therefore, without a
cancelable data type, a safe signature needs to return a
cancellation token, so it would look like this:
sleep
as described above, on cancellation we'd need a way tosignal to the underlying
ScheduledExecutorService
to forcefullyremove the scheduled
Runnable
from its internal queue ofscheduled tasks, ''before'' its execution. Therefore, without a
cancelable data type, a safe signature needs to return a
cancellation token, so it would look like this:
{{{
def sleep(d: FiniteDuration): F[(F[Unit] , F[Unit] )]
}}}
def sleep(d: FiniteDuration): F[(F[Unit] , F[Unit] )]
}}}
This function is returning a tuple, with one
the completion of our sleep and a second
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.
F[Unit]
to wait forthe completion of our sleep and a second
F[Unit]
to cancel thescheduled 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
canceled should the need arise, in order to trigger an early
release of allocated resources.
is only Async is that you can go from any
F[A]
to aF[Fiber[F, A]]
, to participate in race conditions and that can becanceled 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 cancellation
tokens. An Async data type cannot expose for example a
operation that is safe.
conditions, whereas a data type that is only Async cannot do it
without exposing and forcing the user to work with cancellation
tokens. An Async data type cannot expose for example a
start
operation that is safe.
== Resource-safety ==
Concurrent data types are required to cooperate with Bracket.
corresponding
that in the case of bracketCase the
ExitCase.Canceled branch will get executed on cancelation.
Concurrent
being cancelable by law, what this means for thecorresponding
Bracket
is that cancelation can be observed andthat in the case of bracketCase the
ExitCase.Canceled branch will get executed on cancelation.
By default the
and from asyncF, so what this means is that
whatever you can express with
with
cancelable
builder is derived from bracketCase
and from asyncF, so what this means is that
whatever you can express with
cancelable
, you can also expresswith
bracketCase
.For uncancelable, the cancel
signal has no effect on the result of join and
the cancelable token returned by ConcurrentEffect.runCancelable
on evaluation will have no effect if evaluated.
signal has no effect on the result of join and
the cancelable token returned by ConcurrentEffect.runCancelable
on evaluation will have no effect if evaluated.
{{{
F.uncancelable(F.cancelable { cb => f(cb); token }) <-> F.async(f)
}}}
F.uncancelable(F.cancelable { cb => f(cb); token }) <-> F.async(f)
}}}
Sample:
{{{
val F = Concurrent[IO]
val timer = Timer[IO]
val F = Concurrent[IO]
val timer = Timer[IO]
// Normally Timer#sleep yields cancelable tasks
val tick = F.uncancelable(timer.sleep(10.seconds))
val tick = F.uncancelable(timer.sleep(10.seconds))
// This prints "Tick!" after 10 seconds, even if we are
// canceling the Fiber after start:
for {
fiber <- F.start(tick)
_ <- fiber.cancel
_ <- fiber.join
_ <- F.delay { println("Tick!") }
} yield ()
}}}
// canceling the Fiber after start:
for {
fiber <- F.start(tick)
_ <- fiber.cancel
_ <- fiber.join
_ <- F.delay { println("Tick!") }
} yield ()
}}}
When doing bracket or bracketCase,
acquire
and release
operations are guaranteed to be uncancelable as well.- Companion
- object
@implicitNotFound("Could not find an instance of ConcurrentEffect for ${F}")
Type class describing effect data types that are cancelable.
In addition to the algebras of Concurrent and of
Effect, instances must also implement a
runCancelable operation that
triggers the evaluation, suspended in the
also returns a token that can be used for canceling the running
computation.
Effect, instances must also implement a
runCancelable operation that
triggers the evaluation, suspended in the
IO
context, but thatalso returns a token that can be used for canceling the running
computation.
Note this is the safe and generic version of IO.unsafeRunCancelable.
- Companion
- object
@implicitNotFound("Cannot find an implicit value for ContextShift[${F}]:\n* import ContextShift[${F}] from your effects library\n* if using IO, use cats.effect.IOApp or build one with cats.effect.IO.contextShift\n")
ContextShift provides support for shifting execution.
The
from the calling thread to the default execution environment of
shift
method inserts an asynchronous boundary, which moves executionfrom the calling thread to the default execution environment of
F
.The
context, shifting back to the default execution context after the task completes.
evalOn
method provides a way to evaluate a task on a specific executioncontext, shifting back to the default execution context after the task completes.
This is NOT a type class, as it does not have the coherence
requirement.
requirement.
- Companion
- object
@implicitNotFound("Could not find an instance of Effect for ${F}")
Type for signaling the exit condition of an effectful
computation, that may either succeed, fail with an error or
get canceled.
computation, that may either succeed, fail with an error or
get canceled.
sealed abstract case class ExitCode
Represents the exit code of an application.
code
is constrained to a range from 0 to 255, inclusive.- Companion
- object
Fiber
represents the (pure) result of a Concurrent data type (e.g. IO)being started concurrently and that can be either joined or canceled.
You can think of fibers as being lightweight threads, a fiber being a
concurrency primitive for doing cooperative multi-tasking.
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 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
}
}}}
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
}
}}}
- Companion
- object
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).
side effect, where the result of that side effect may be obtained
synchronously (via return) or asynchronously (via callback).
IO
values are pure, immutable values and thus preservereferential transparency, being usable in functional programming.
An
IO
is a data structure that represents just a descriptionof a side effectful computation.
IO
can describe synchronous or asynchronous computations that:-
on evaluation yield exactly one result
-
can end in either success or failure and in case of failure
flatMap
chains get short-circuited (IO
implementing
the algebra ofMonadError
) -
can be canceled, but note this capability relies on the
user to provide cancellation logic
Effects described via 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:
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 ioa = IO { println("hey!") }
val program = for {
_ <- ioa
_ <- ioa
} yield ()
_ <- 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.
each time it is sequenced in the monadic chain.
IO
is trampolined in its flatMap
evaluation. This means thatyou can safely call
flatMap
in a recursive function of arbitrarydepth, without fear of blowing the stack.
{{{
def fib(n: Int, a: Long = 0, b: Long = 1): IO[Long] =
IO(a + b).flatMap { b2 =>
if (n > 0)
fib(n - 1, b, b2)
else
IO.pure(a)
}
}}}
def fib(n: Int, a: Long = 0, b: Long = 1): IO[Long] =
IO(a + b).flatMap { b2 =>
if (n > 0)
fib(n - 1, b, b2)
else
IO.pure(a)
}
}}}
- Companion
- object
trait IOApp
-
If completed with
ExitCode.Success
, the main method exits and
shutdown is handled by the platform. -
If the
IO
raises an error, the stack trace is printed to
standard error andsys.exit(1)
is called.
When a shutdown is requested via a signal, the
we wait for the
with the numeric value of the signal plus 128.
IO
is canceled andwe wait for the
IO
to release any resources. The process exitswith the numeric value of the signal plus 128.
{{{
import cats.effect._
import cats.syntax.all._
import cats.effect._
import cats.syntax.all._
object MyApp extends IOApp {
def run(args: List[String] ): IO[ExitCode] =
args.headOption match {
case Some(name) =>
IO(println(s"Hello, ${name}.")).as(ExitCode.Success)
case None =>
IO(System.err.println("Usage: MyApp name")).as(ExitCode(2))
}
}
}}}
def run(args: List[String] ): IO[ExitCode] =
args.headOption match {
case Some(name) =>
IO(println(s"Hello, ${name}.")).as(ExitCode.Success)
case None =>
IO(System.err.println("Usage: MyApp name")).as(ExitCode(2))
}
}
}}}
- Companion
- object
The
allocation of a resource, along with its finalizer.
Resource
is a data structure that captures the effectfulallocation of a resource, along with its finalizer.
This can be used to wrap expensive resources. Example:
{{{
def open(file: File): Resource[IO, BufferedReader] =
Resource(IO {
val in = new BufferedReader(new FileReader(file))
(in, IO(in.close()))
})
}}}
def open(file: File): Resource[IO, BufferedReader] =
Resource(IO {
val in = new BufferedReader(new FileReader(file))
(in, IO(in.close()))
})
}}}
Usage is done via use and note that resource usage nests,
because its implementation is specified in terms of Bracket:
because its implementation is specified in terms of Bracket:
{{{
open(file1).use { in1 =>
open(file2).use { in2 =>
readFiles(in1, in2)
}
}
}}}
open(file1).use { in1 =>
open(file2).use { in2 =>
readFiles(in1, in2)
}
}
}}}
Resource
forms a MonadError
on the resource type when theeffect type has a
cats.MonadError
instance. Nested resources arereleased in reverse order of acquisition. Outer resources are
released even if an inner use or release fails.
{{{
def mkResource(s: String) = {
val acquire = IO(println(s"Acquiring $$s")) *> IO.pure(s)
def release(s: String) = IO(println(s"Releasing $$s"))
Resource.make(acquire)(release)
}
def mkResource(s: String) = {
val acquire = IO(println(s"Acquiring $$s")) *> IO.pure(s)
def release(s: String) = IO(println(s"Releasing $$s"))
Resource.make(acquire)(release)
}
val r = for {
outer <- mkResource("outer")
inner <- mkResource("inner")
} yield (outer, inner)
outer <- mkResource("outer")
inner <- mkResource("inner")
} yield (outer, inner)
r.use { case (a, b) =>
IO(println(s"Using $$a and $$b"))
}
}}}
IO(println(s"Using $$a and $$b"))
}
}}}
On evaluation the above prints:
{{{
Acquiring outer
Acquiring inner
Using outer and inner
Releasing inner
Releasing outer
}}}
{{{
Acquiring outer
Acquiring inner
Using outer and inner
Releasing inner
Releasing outer
}}}
A
the following node types and that can be interpreted if needed:
Resource
is nothing more than a data structure, an ADT, described bythe following node types and that can be interpreted if needed:
Normally users don't need to care about these node types, unless conversions
from
into a streaming data type).
from
Resource
into something else is needed (e.g. conversion from Resource
into a streaming data type).
- Type Params
- A
-
the type of resource
- F
-
the effect type in which the resource is allocated and released
- Companion
- object
@implicitNotFound("Could not find an instance of Sync for ${F}")
A monad that can suspend the execution of side effects
in the
in the
F[_]
context.- Companion
- object
@implicitNotFound("Could not find an instance of SyncEffect for ${F}")
A monad that can suspend side effects into the
that supports only synchronous lazy evaluation of these effects.
F
context andthat supports only synchronous lazy evaluation of these effects.
- Companion
- object
A pure abstraction representing the intention to perform a
side effect, where the result of that side effect is obtained
synchronously.
side effect, where the result of that side effect is obtained
synchronously.
SyncIO
is similar to IO, but does not support asynchronouscomputations. Consequently, a
SyncIO
can be run synchronouslyto obtain a result via
unsafeRunSync
. This is unlikeIO#unsafeRunSync
, which cannot be safely called in general --doing so on the JVM blocks the calling thread while the
async part of the computation is run and doing so on Scala.js
throws an exception upon encountering an async boundary.
- Companion
- object
@implicitNotFound("Cannot find an implicit value for Timer[${F}]:\n* import Timer[${F}] from your effects library\n* if using IO, use cats.effect.IOApp or build one with cats.effect.IO.timer\n")
Timer is a scheduler of tasks.
This is the purely functional equivalent of:
-
Java's
ScheduledExecutorService -
JavaScript's
setTimeout.
It provides:
-
the ability to get the current time
-
ability to delay the execution of a task with a specified time duration
It does all of that in an
side effects and is capable of asynchronous execution (e.g. IO).
F
monadic context that can suspendside effects and is capable of asynchronous execution (e.g. IO).
This is NOT a type class, as it does not have the coherence
requirement.
requirement.
- Companion
- object
Types
A cancelation token is an effectful action that is
able to cancel a running task.
able to cancel a running task.
This is just an alias in order to clarify the API.
For example seeing
can be more readable.
For example seeing
CancelToken[IO]
instead of IO[Unit]
can be more readable.
Cancelation tokens usually have these properties:
-
they suspend over side effectful actions on shared state
-
they need to be idempotent
Note that in the case of well behaved implementations like
that of IO idempotency is taken care of by its internals
whenever dealing with cancellation tokens, but idempotency
is a useful property to keep in mind when building such values.
that of IO idempotency is taken care of by its internals
whenever dealing with cancellation tokens, but idempotency
is a useful property to keep in mind when building such values.