Skip to content

Commit

Permalink
Add guide on compile. (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
zainab-ali authored Sep 14, 2024
1 parent d800e5b commit ceadbfb
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 48 deletions.
1 change: 1 addition & 0 deletions docs/reference/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This is an aquascape-based reference guide to fs2.

Learn [how to read aquascapes](../README.md#how-to-read-the-diagrams), then learn about an operator:

- [compile.toList, compile.last, compile.onlyOrError, compile.count, compile.drain](compile.md)
- [take, takeWhile, takeThrough, takeRight](take.md)
- [drop, dropWhile, dropThrough, dropLast, dropLastIf](drop.md)
- [filter, filterNot, filterWithPrevious, changes, mapFilter](filter.md)
Expand Down
81 changes: 81 additions & 0 deletions docs/reference/compile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{%
pageid = DocsReferenceCompile
%}

# compile

This page is a reference guide for the `compile` operations. It describes the behaviour of `compile.toList`, `compile.last`, `compile.onlyOrError`, `compile.count` and `compile.drain`.

## Behaviour

The `compile` operations are used to convert a stream into a single value or effect. Compiling a stream is analagous to compiling a program.

Streams with no side effects are termed pure streams and have the effect type of `Pure`. These are converted directly into a value. For example, `Stream('a','b').compile.toList` is an expression of type `List[Char]` which evaluates to the value `List('a','b')`.

Streams with side effects are converted into their effect type. For example, `Stream[IO, Char]('a','b').compile.toList` is an expression of type `IO[List[Char]]`. When run, it evaluates to `List('a', 'b')`.

### toList

The `compile.toList` operation repeatedly pulls on a stream until it is done, then results in a list of outputted values.

The following example, a pure input stream outputting the characters `'a'` and `'b'` is compiled to a value of `List('a', 'b')`.

@:example(toList) {
drawChunked = false
}


`compile.toList` can be used on pure or side-effecting streams for which the output can be held in memory.

Using `compile.toList` on large streams will result in memory issues as the output is accumulated in a list.

### last

The `compile.last` operation repeatedly pulls on a stream until it is done, then results in the last outputted value.

Experiment with the behaviour of `compile.last` on non-empty and empty streams using the following example.

@:exampleWithInput(last) {
drawChunked = false
}

Note that if the stream outputs no elements, the result of `compile.last` is `None`.

`compile.last` can be used on pure or side-effecting streams. The input stream must be finite for `compile.last` to terminate.


### onlyOrError

The `compile.onlyOrError` operation pulls on a stream and outputs the first value.

It expects a singleton stream. If the stream outputs more than one element, or does not output any elements, an error is raised.

Experiment with the behaviour of `compile.onlyOrError` on empty, singleton, and non-empty streams using the following example.

@:exampleWithInput(onlyOrError) {
drawChunked = false
}

`compile.onlyOrError` can be used on effectful streams with a monad error instance. The input stream must be finite for `compile.onlyOrError` to terminate.

### count

The `compile.count` operation repeatedly pulls on a stream until it is done, then results in a count of the number of elements outputted.

@:example(count) {
drawChunked = false
}

`compile.count` can be used on pure or side-effecting streams. The input stream must be finite for `compile.count` to terminate.

### drain

The `compile.drain` operation repeatedly oulls on a stream until it is done, then results in a `()` value.

In the following example, the input stream executes side effect `97` before outputting `a`, then executes side-effect `98` before outputting `b`. Both `a` and `b` are discarded, but the side effects are still run.

@:example(drain) {
drawChunked = false
}

`compile.drain` is used for input streams which are side-effecting. The side-effects in the stream are run, and the output is discarded. If the input is infinite, it will continue to pull on the stream and will never terminate.
48 changes: 0 additions & 48 deletions examples/src/main/scala/Examples.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,6 @@ import fs2.*
import scala.concurrent.duration.*
import scala.scalajs.js.annotation.JSExportTopLevel

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

@JSExportTopLevel("BasicCompileDrain")
object BasicCompileDrain extends Example {
def apply(using Scape[IO]): StreamCode =
Expand All @@ -50,42 +38,6 @@ object BasicCompileDrain extends Example {
)
}

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

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

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

@JSExportTopLevel("ChunkingChunkRepartition")
object ChunkingChunkRepartition extends Example {
def apply(using Scape[IO]): StreamCode =
Expand Down
104 changes: 104 additions & 0 deletions examples/src/main/scala/compile.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* 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 fs2.*

import scala.scalajs.js.annotation.JSExport
import scala.scalajs.js.annotation.JSExportTopLevel

@JSExportTopLevel("DocsReferenceCompile")
object compile {
def takeFiniteInputBox(max: Int): InputBox[Int] = InputBox.int(
labelText = "n (elements to take)",
defaultValue = 1,
min = 0,
max = max
)

@JSExport
val toList = new Example {
def apply(using Scape[IO]): StreamCode =
code(
Stream('a', 'b')
.stage("Stream('a','b')")
.compile
.toList
.compileStage("compile.toList")
)
}

@JSExport
val last = new ExampleWithInput[Int] {
val inputBox: InputBox[Int] = takeFiniteInputBox(2)
def apply(n: Int)(using Scape[IO]): StreamCode =
code(
Stream('a', 'b')
.take(n)
.stage(s"Stream('a','b').take($n)")
.compile
.last
.compileStage("compile.last")
)
}

@JSExport
val onlyOrError = new ExampleWithInput[Int] {
val inputBox: InputBox[Int] = takeFiniteInputBox(2)
def apply(n: Int)(using Scape[IO]): StreamCode =
code(
Stream('a', 'b')
.take(n)
.stage(s"Stream('a','b').take($n)")
.compile
.onlyOrError
.compileStage("compile.onlyOrError")
)
}

@JSExport
val count = new Example {
def apply(using Scape[IO]): StreamCode =
code(
Stream('a', 'b')
.stage("Stream('a','b')")
.compile
.count
.compileStage("compile.count")
)
}

@JSExport
val drain = new Example {
def apply(using Scape[IO]): StreamCode =
code(
Stream('a', 'b')
.evalTap(c => IO(c.toInt).trace().void)
.stage("Stream('a','b').evalTap(…)")
.compile
.drain
.compileStage("compile.drain")
)
}

}

0 comments on commit ceadbfb

Please sign in to comment.