Skip to content

Chunk

Chunk<A> is an immutable, ordered sequence of values. It behaves like a read-only array — you can map, filter, take, and index into it — but every operation returns a new Chunk rather than mutating the original, and it compares by value (Equal.equals) rather than by reference. Chunks are the backing collection for Streaming: a stream delivers its elements in chunks, so you will see Chunk whenever you pull data out of a Stream.

import { Chunk } from "effect"
// Build a chunk and transform it — each step yields a fresh, immutable Chunk
const evensDoubled = Chunk.make(1, 2, 3, 4, 5, 6).pipe(
Chunk.filter((n) => n % 2 === 0),
Chunk.map((n) => n * 2)
)
// Convert back to a plain array at the boundary of your code
console.log(Chunk.toReadonlyArray(evensDoubled)) // [4, 8, 12]

Each pipe step produces a new Chunk; the original make(1, …, 6) is never changed. When you need a normal array (to return from an API, say), convert with Chunk.toReadonlyArray.

import { Chunk } from "effect"
const empty = Chunk.empty<number>()
const explicit = Chunk.make(1, 2, 3) // from values
const fromArray = Chunk.fromIterable([1, 2, 3]) // from any Iterable
const single = Chunk.of(1) // one element

append / prepend add a single element; appendAll concatenates two chunks. All of them return a new chunk.

import { Chunk } from "effect"
const base = Chunk.make(2, 3)
console.log(Chunk.toReadonlyArray(Chunk.prepend(base, 1))) // [1, 2, 3]
console.log(Chunk.toReadonlyArray(Chunk.append(base, 4))) // [2, 3, 4]
console.log(
Chunk.toReadonlyArray(Chunk.appendAll(base, Chunk.make(4, 5)))
) // [2, 3, 4, 5]

Chunk.get returns an Option for safe indexing; Chunk.head and Chunk.last are also Option-returning because a chunk may be empty. Chunk.size reports the length.

import { Chunk } from "effect"
const chunk = Chunk.make(10, 20, 30)
console.log(Chunk.size(chunk)) // 3
console.log(Chunk.get(chunk, 1)) // { _id: 'Option', _tag: 'Some', value: 20 }
console.log(Chunk.get(chunk, 5)) // { _id: 'Option', _tag: 'None' }
console.log(Chunk.head(chunk)) // { _id: 'Option', _tag: 'Some', value: 10 }

Because out-of-bounds access yields None rather than undefined, you cannot forget to handle the empty/missing case.

The familiar collection operations are all here and all immutable:

import { Chunk } from "effect"
const chunk = Chunk.make(1, 2, 3, 4, 5)
console.log(Chunk.toReadonlyArray(Chunk.take(chunk, 2))) // [1, 2]
console.log(Chunk.toReadonlyArray(Chunk.drop(chunk, 2))) // [3, 4, 5]
console.log(Chunk.toReadonlyArray(Chunk.reverse(chunk))) // [5, 4, 3, 2, 1]
console.log(Chunk.reduce(chunk, 0, (sum, n) => sum + n)) // 15

Two chunks built separately are equal when their elements are equal, so a Chunk is safe to compare in tests and to use as a value inside other structurally-compared types.

import { Chunk, Equal } from "effect"
console.log(Equal.equals(Chunk.make(1, 2, 3), Chunk.make(1, 2, 3))) // true
console.log(Equal.equals(Chunk.make(1, 2), Chunk.make(1, 2, 3))) // false

For everyday in-memory lists, a plain ReadonlyArray and the Array module are perfectly fine. Reach for Chunk when you are working with Streaming (where it is the native element container), or when you want guaranteed immutability and structural equality for a sequence. Convert with Chunk.fromIterable going in and Chunk.toReadonlyArray coming out.