diff --git a/docs/resources/bracket.md b/docs/resources/bracket.md index ad64d4f..2f9bbc5 100644 --- a/docs/resources/bracket.md +++ b/docs/resources/bracket.md @@ -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 +} diff --git a/examples/src/main/scala/Examples.scala b/examples/src/main/scala/Examples.scala index 1d29010..6facfd5 100644 --- a/examples/src/main/scala/Examples.scala +++ b/examples/src/main/scala/Examples.scala @@ -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 = @@ -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") @@ -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 = diff --git a/examples/src/main/scala/bracket.scala b/examples/src/main/scala/bracket.scala new file mode 100644 index 0000000..15cc2d9 --- /dev/null +++ b/examples/src/main/scala/bracket.scala @@ -0,0 +1,182 @@ +/* + * Copyright 2023 Zainab Ali + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package docs.reference + +import aquascape.* +import aquascape.examples.* +import aquascape.examples.syntax.given +import cats.Show +import cats.effect.* +import cats.effect.IO +import cats.syntax.all.* +import fs2.* + +import scala.concurrent.duration.* +import scala.scalajs.js.annotation.JSExport +import scala.scalajs.js.annotation.JSExportTopLevel + +@JSExportTopLevel("DocsReferenceBracket") +object bracket { + @JSExport + val bracket = new Example { + def apply(using Scape[IO]): StreamCode = + code( + Stream + .bracket(IO('a').trace())(_ => IO('b').trace().void) + .stage("Stream.bracket(…)(…)") + .compile + .toList + .compileStage("compile.toList") + ) + } + + @JSExport + val bracketAppend = new Example { + def apply(using Scape[IO]): StreamCode = + code { + val ab = Stream + .bracket(IO('a').trace())(_ => IO('b').trace().void) + .stage("ab") + val xy = Stream + .bracket(IO('x').trace())(_ => IO('y').trace().void) + .stage("xy") + (ab ++ xy) + .stage("ab ++ xy") + .compile + .toList + .compileStage("compile.toList") + } + } + + @JSExport + val resource = new Example { + def apply(using Scape[IO]): StreamCode = + code { + val res = Resource.make(IO('a').trace())(_ => IO('b').trace().void) + Stream + .resource(res) + .stage("Stream.resource(res)") + .compile + .toList + .compileStage("compile.toList") + } + } + + @JSExport + val bracketCase = new Example { + def apply(using Scape[IO]): StreamCode = + code( + Stream + .bracketCase(IO('a').trace())((_, exitCase) => + IO(exitCase.show).trace().void + ) + .stage("Stream.bracketCase(…)(…)") + .compile + .toList + .compileStage("compile.toList") + ) + } + + @JSExport + val bracketCaseErrored = new Example { + def apply(using Scape[IO]): StreamCode = + code( + Stream + .bracketCase(IO('a').trace())((_, exitCase) => + IO(exitCase.show).trace().void + ) + .stage("Stream.bracketCase(…)(…)") + .flatMap(_ => Stream.raiseError[IO](Err)) + .stage("flatMap(…)") + .compile + .toList + .compileStage("compile.toList") + ) + } + + @JSExport + val bracketCaseCanceled = new Example { + def apply(using Scape[IO]): StreamCode = + code( + Stream + .bracketCase(IO('a').trace())((_, exitCase) => + IO(exitCase.show).trace().void + ) + .stage("Stream.bracketCase(…)(…)") + .flatMap(_ => Stream.sleep[IO](10.seconds)) + .stage("flatMap(…)") + .interruptAfter(1.second) + .compile + .toList + .compileStage("compile.toList") + ) + } + + @JSExport + val onFinalize = new Example { + def apply(using Scape[IO]): StreamCode = + code( + Stream('a') + .onFinalize(IO('b').trace().void) + .stage("Stream('a').onFinalize(…)") + .compile + .toList + .compileStage("compile.toList") + ) + } + + @JSExport + val onFinalizeCase = new Example { + def apply(using Scape[IO]): StreamCode = + code( + Stream('a') + .onFinalizeCase(exitCase => IO(exitCase.show).trace().void) + .stage("Stream('a').onFinalizeCase(…)") + .compile + .toList + .compileStage("compile.toList") + ) + } + + @JSExport + val bracketAcquireError = new Example { + def apply(using Scape[IO]): StreamCode = + code( + Stream + .bracket(IO.raiseError[Char](Err))(_ => IO('b').trace().void) + .stage("Stream.bracket(…)(…)") + .onFinalizeCase(exitCase => IO(exitCase.show).trace().void) + .compile + .toList + .compileStage("compile.toList") + ) + } + + @JSExport + val bracketReleaseError = new Example { + def apply(using Scape[IO]): StreamCode = + code( + Stream + .bracket(IO('a'))(_ => IO.raiseError(Err).trace().void) + .stage("Stream.bracket(…)(…)") + .onFinalizeCase(exitCase => IO(exitCase.show).trace().void) + .compile + .toList + .compileStage("compile.toList") + ) + } +}