Skip to content

Introduction to Streams

In this guide, we’ll explore the concept of a Stream<A, E, R>. A Stream is a program description that, when executed, can emit zero or more values of type A, handle errors of type E, and operates within a context of type R.

Streams are particularly handy whenever you’re dealing with sequences of values over time. They can serve as replacements for observables, node streams, and AsyncIterables.

Think of a Stream as an extension of an Effect. While an Effect<A, E, R> represents a program that requires a context of type R, may encounter an error of type E, and always produces a single result of type A, a Stream<A, E, R> takes this further by allowing the emission of zero or more values of type A.

To clarify, let’s examine some examples using Effect:

1
import {
import Effect
Effect
,
import Chunk
Chunk
,
import Option
Option
} from "effect"
2
3
// An Effect that fails with a string error
4
const
const failedEffect: Effect.Effect<never, string, never>
failedEffect
=
import Effect
Effect
.
const fail: <string>(error: string) => Effect.Effect<never, string, never>

Creates an `Effect` that represents a recoverable error. This `Effect` does not succeed but instead fails with the provided error. The failure can be of any type, and will propagate through the effect pipeline unless handled. Use this function when you want to explicitly signal an error in an `Effect` computation. The failed effect can later be handled with functions like {@link catchAll } or {@link catchTag } .

fail
("fail!")
5
6
// An Effect that produces a single number
7
const
const oneNumberValue: Effect.Effect<number, never, never>
oneNumberValue
=
import Effect
Effect
.
const succeed: <number>(value: number) => Effect.Effect<number, never, never>

Creates an `Effect` that succeeds with the provided value. Use this function to represent a successful computation that yields a value of type `A`. The effect does not fail and does not require any environmental context.

succeed
(3)
8
9
// An Effect that produces a chunk of numbers
10
const
const oneListValue: Effect.Effect<Chunk.NonEmptyChunk<number>, never, never>
oneListValue
=
import Effect
Effect
.
const succeed: <Chunk.NonEmptyChunk<number>>(value: Chunk.NonEmptyChunk<number>) => Effect.Effect<Chunk.NonEmptyChunk<number>, never, never>

Creates an `Effect` that succeeds with the provided value. Use this function to represent a successful computation that yields a value of type `A`. The effect does not fail and does not require any environmental context.

succeed
(
import Chunk
Chunk
.
const make: <[number, number, number]>(as_0: number, as_1: number, as_2: number) => Chunk.NonEmptyChunk<number>

Builds a `NonEmptyChunk` from an non-empty collection of elements.

make
(1, 2, 3))
11
12
// An Effect that produces an optional number
13
const
const oneOption: Effect.Effect<Option.Option<number>, never, never>
oneOption
=
import Effect
Effect
.
const succeed: <Option.Option<number>>(value: Option.Option<number>) => Effect.Effect<Option.Option<number>, never, never>

Creates an `Effect` that succeeds with the provided value. Use this function to represent a successful computation that yields a value of type `A`. The effect does not fail and does not require any environmental context.

succeed
(
import Option
Option
.
const some: <number>(value: number) => Option.Option<number>

Creates a new `Option` that wraps the given value.

some
(1))

In each case, the Effect always ends with exactly one value. There is no variability; you always get one result.

Now, let’s shift our focus to Stream. A Stream represents a program description that shares similarities with Effect, it requires a context of type R, may signal errors of type E, and yields values of type A. However, the key distinction is that it can yield zero or more values.

Here are the possible scenarios for a Stream:

  • An Empty Stream: It can end up empty, representing a stream with no values.
  • A Single-Element Stream: It can represent a stream with just one value.
  • A Finite Stream of Elements: It can represent a stream with a finite number of values.
  • An Infinite Stream of Elements: It can represent a stream that continues indefinitely, essentially an infinite stream.

Let’s see these scenarios in action:

1
import {
import Stream
Stream
} from "effect"
2
3
// An empty Stream
4
const
const emptyStream: Stream.Stream<never, never, never>
emptyStream
=
import Stream
Stream
.
const empty: Stream.Stream<never, never, never>

The empty stream.

empty
5
6
// A Stream with a single number
7
const
const oneNumberValueStream: Stream.Stream<number, never, never>
oneNumberValueStream
=
import Stream
Stream
.
const succeed: <number>(value: number) => Stream.Stream<number, never, never>

Creates a single-valued pure stream.

succeed
(3)
8
9
// A Stream with a range of numbers from 1 to 10
10
const
const finiteNumberStream: Stream.Stream<number, never, never>
finiteNumberStream
=
import Stream
Stream
.
const range: (min: number, max: number, chunkSize?: number) => Stream.Stream<number>

Constructs a stream from a range of integers, including both endpoints.

range
(1, 10)
11
12
// An infinite Stream of numbers starting from 1 and incrementing
13
const
const infiniteNumberStream: Stream.Stream<number, never, never>
infiniteNumberStream
=
import Stream
Stream
.
const iterate: <number>(value: number, next: (value: number) => number) => Stream.Stream<number, never, never>

The infinite stream of iterative function application: a, f(a), f(f(a)), f(f(f(a))), ...

iterate
(1, (
(parameter) n: number
n
) =>
(parameter) n: number
n
+ 1)

In summary, a Stream is a versatile tool for representing programs that may yield multiple values, making it suitable for a wide range of tasks, from processing finite lists to handling infinite sequences.