Daniel Spiewak on gitter
Think about the closing action of a bracket. I'll use
IO
andResource
here as the archetypical examples, but Stream is also a very good exemplar, and obviously all of the cats-effect implementors have IO-like semantics in this regard.
So the closing action of a bracket with IO will be sequenced after the result is produced (or an error, or cancelation). Thus, imagine a chain like
fa1 *> fa2 *> fa3
. If fa1 is created by bracket, then the finalizer will be sequenced before fa2. Thus, associating to the left over bind.
This is different with
Resource
, and it represents the critical distinction of that type. If you have a chainra1 *> ra2 *> ra3
, then ra1's finalizer will be sequenced after ra3, and perhaps after other things. The region scope is very different, and the bracket thus associates to the right over bind.
def bracketId(id: String): IO[String] =
Bracket[IO, Throwable].bracket(putStrLn(s"Acquire $id").as(id))(_.pure[IO])(_ => putStrLn(s"Release: $id"))
bracketId("1") *> bracketId("2") *> bracketId("3")
// Acquire 1
// Release: 1
// Acquire 2
// Release: 2
// Acquire 3
// Release: 3
putStrLn("Acquire 1").bracket(_ =>
putStrLn("Acquire 2").bracket(_.pure[IO])(_ => putStrLn("Release 2)"))
)(_ => putStrLn("Release 1")
// acquire 1
// acquire 2
// release 2
// release 1
Resource on fs2:
val s1 = Stream.emit(1)
val s2 = Stream.awakeEvery[IO](100.millis).evalMap(c => putStrLn(s"Elapsed: $c"))
// Without resource
(s1.concurrently(s2).compile.lastOrError >> IO.sleep(10.seconds)).run // Prints nothing
// With resource: prints during the lifetime of the resource.
s1.concurrently(s2).compile.resource.lastOrError.use(_ => IO.sleep(3.seconds)).run
// Elapsed: 113548279 nanoseconds
// Elapsed: 217579663 nanoseconds
// ...