Skip to content

Commit

Permalink
Add reference guide on bracket (#66)
Browse files Browse the repository at this point in the history
* Add reference guide to bracket.

* Delete unused examples.
  • Loading branch information
zainab-ali authored Aug 29, 2024
1 parent ce4588d commit 19a971b
Show file tree
Hide file tree
Showing 3 changed files with 294 additions and 123 deletions.
119 changes: 112 additions & 7 deletions docs/resources/bracket.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,117 @@
#bracket
## bracket
{% pageid = DocsReferenceBracket %}

@:example(ResourcesBracket)
# bracket

## raising errors
This page is a reference guide to the `bracket` family of operators. It describes:

@:example(ResourcesBracketRaisingErrors)
- The behaviour of `Stream.bracket`, `Stream.bracketCase`, `Stream.resource`, `onFinalize` and `onFinalizeCase`.
- The error propagation of bracket operators.

As bracket operators have standard chunk propagation, this is not described.

## handing errors
## Behaviour

@:example(ResourcesBracketHandlingErrors)
### bracket

The `Stream.bracket(acquire)(release)` operator constructs a stream from an `acquire` and `release` effect. The stream outputs a single element as a result of running `acquire`. When the stream terminates, the `release` effect is guaranteed to run, regardless of whether the stream terminates successfully, errors or is cancelled.

The following example shows how `Stream.bracket` constructs a single element stream. The `acquire` effect evaluates to a character `a`, which is outputted. When the stream is done, the `release` effect `IO('b')` is evaluated.

@:example(bracket) {
drawChunked = false
}

#### Releasing resources mid-program

The `release` effect is run at the end of the stream's lifetime, not at the end of the entire program.

This is shown in the following example.
The streams `ab` and `xy` are appended using the `++` operator. Both streams are constructed with `release` effects.


@:example(bracketAppend) {
drawChunked = false
}

When stream `ab` terminates, its release effect `IO('b')` is evaluated. Note this this is mid-program: the stream resulting from `++` has not terminated.

### resource

The `Stream.resource` operator constructs a stream from a `cats.effect.Resource`. The `acquire` and `release` effects of the resource are run similarly to `Stream.bracket`.

The following example lifts a resource with an acquire effect of `IO('a')` and release effect of `IO('b')` into a stream.

@:example(resource) {
drawChunked = false
}

### bracketCase

The `Stream.bracketCase` operator behaves similarly to `Stream.bracket`, but uses the `ExitCase` to construct the `release` effect. The exit case describes how the stream terminated. It is either `Errored`, `Succeeded` or `Canceled`.

The following example shows the exit case of a successful stream.

@:example(bracketCase) {
drawChunked = false
}

If an error is raised over the lifetime of the stream, its exit case will be `Errored`. In the following example, a stream is constructed from `bracketCase`, and then composed with an error-raising stream using `flatMap`.

The stream terminates with an exit case of `Errored` due to the raised error.

@:example(bracketCaseErrored) {
drawChunked = false
}

If the stream is canceled due to fiber cancelation or interruption, its exit case will be `Canceled`. In the following example, the stream constructed from `bracketCase` is composed with a long-running `Stream.sleep`. It is then composed with a short-running `interruptAfter` operator. The resulting stream is canceled after 1 second.

@:example(bracketCaseCanceled) {
drawChunked = false
}

Note that the exit case printed by the release effect is `Canceled`.

### onFinalize

The `onFinalize` operator evaluates a release effect when its input stream terminates.

`input.onFinalize(release)` is equivalent to `Stream.bracket(IO.unit)(release).flatMap(_ => input)`

In the following example, an input stream outputs a single `a` character. Once it is done, the resource effect `IO('b')`, termed a finalizer, is run.

@:example(onFinalize) {
drawChunked = false
}

### onFinalizeCase

The `onFinalizeCase` operator behaves similarly to `onFinalize`, but uses the exit case of the input stream to construct the release effect. The exit case is calculated similarly to `bracketCase`.

@:example(onFinalizeCase) {
drawChunked = false
}

## Error propagation

The acquire and release effects may raise errors. If so, these are propagated to the resulting stream.

### Errors raised in acquire

The release effect is only evaluated if the acquire effect was successful. In the following example, the acquire effect raises an error `Err`.

@:example(bracketAcquireError) {
drawChunked = false
}

Note that the release effect `IO('b')` is never run and the resulting stream terminates with an exit case of `Errored`.


### Errors raised in release

Raising errors in the release effect is not recommended.

If an error is raised in the release effect, the exit case of the resulting stream is `Errored`. Any finalizers in the resulting stream are executed.

@:example(bracketReleaseError) {
drawChunked = false
}
116 changes: 0 additions & 116 deletions examples/src/main/scala/Examples.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,38 +86,6 @@ object BasicCompileOnlyOrError extends Example {
)
}

@JSExportTopLevel("ChunkingChunkChunkLimit")
object ChunkingChunkChunkLimit extends Example {
def apply(using Scape[IO]): StreamCode =
code(
Stream('a', 'b', 'c')
.stage("Stream('a','b','c')")
.chunkLimit(2)
.unchunks
.stage("chunkLimit(2).unchunks")
.compile
.toList
.compileStage("compile.toList")
)
}

@JSExportTopLevel("ChunkingChunkChunkMin")
object ChunkingChunkChunkMin extends Example {
def apply(using Scape[IO]): StreamCode =
code(
Stream('a', 'b', 'c')
.chunkLimit(1)
.unchunks
.stage("Stream('a','b','c')…unchunks")
.chunkMin(2)
.unchunks
.stage("chunkMin(2).unchunks")
.compile
.toList
.compileStage("compile.toList")
)
}

@JSExportTopLevel("ChunkingChunkRepartition")
object ChunkingChunkRepartition extends Example {
def apply(using Scape[IO]): StreamCode =
Expand Down Expand Up @@ -182,20 +150,6 @@ object BufferingBufferBy extends Example {
)
}

@JSExportTopLevel("CombiningAppend")
object CombiningAppend extends Example {
def apply(using Scape[IO]): StreamCode =
code(
(Stream('a', 'b')
.stage("Stream('a','b')")
++ Stream('c').stage("Stream('c')"))
.stage("++")
.compile
.toList
.compileStage("compile.toList")
)
}

object Err extends Throwable("Err")

@JSExportTopLevel("CombiningParZip")
Expand Down Expand Up @@ -348,76 +302,6 @@ object EffectsParEvalMapUnordered extends Example {
)
}

@JSExportTopLevel("ResourcesBracket")
object ResourcesBracket extends Example {
def apply(using Scape[IO]): StreamCode =
code(
Stream
.bracket(IO("abc").trace())(_ => IO("d").trace().void)
.stage("Stream.bracket(…)")
.flatMap { str =>
Stream
.emits(str.toList)
.stage("Stream.emits(str.toList)")
}
.stage("flatMap {…}")
.compile
.toList
.compileStage("compile.toList")
)
}
@JSExportTopLevel("ResourcesBracketRaisingErrors")
object ResourcesBracketRaisingErrors extends Example {
def apply(using Scape[IO]): StreamCode =
code(
Stream
.bracket(IO("abc").trace())(_ => IO("d").trace().void)
.stage("Stream.bracket(…)")
.flatMap { str =>
Stream
.emits(str.toList)
.stage("Stream.emits(str.toList)")
.evalTap(x => IO.raiseWhen(x == 'b')(Err))
.stage("evalTap(…)")
}
.stage("flatMap {…}")
.compile
.toList
.compileStage("compile.toList")
)
}

@JSExportTopLevel("ResourcesBracketHandlingErrors")
object ResourcesBracketHandlingErrors extends Example {
def apply(using Scape[IO]): StreamCode =
code(
Stream
.bracket(IO("abc").trace())(_ => IO("d").trace().void)
.stage("Stream.bracket(…)")
.flatMap { str =>
Stream
.emits(str.toList)
.stage("Stream.emits(str.toList)")
.evalTap(x => IO.raiseWhen(x == 'b')(Err))
.stage("evalTap(…)")
}
.stage("flatMap1")
.flatMap { str =>
Stream(str)
.repeatN(2)
.stage("Stream.(str).repeatN(2)")
.evalTap(x => IO.raiseWhen(x == 'b')(Err))
.stage("evalTap1(…)")
}
.stage("flatMap {…}")
.handleErrorWith(_ => Stream('e', 'f').stage("Stream('e','f')"))
.stage("handleErrorWith(…)")
.compile
.toList
.compileStage("compile.toList")
)
}

@JSExportTopLevel("TimeAwakeEvery")
object TimeAwakeEvery extends Example {
def apply(using Scape[IO]): StreamCode =
Expand Down
Loading

0 comments on commit 19a971b

Please sign in to comment.