/**
* This module provides utility functions for working with Iterables in TypeScript.
*
* Iterables are objects that implement the iterator protocol, allowing them to be
* consumed with `for...of` loops, spread syntax, and other iteration constructs.
* This module provides a comprehensive set of functions for creating, transforming,
* and working with iterables in a functional programming style.
*
* Unlike arrays, iterables can be lazy and potentially infinite, making them suitable
* for stream processing and memory-efficient data manipulation. All functions in this
* module preserve the lazy nature of iterables where possible.
*
* @example
* ```ts
* import { Iterable, Option } from "effect"
*
* // Create iterables
* const numbers = Iterable.range(1, 5)
* const doubled = Iterable.map(numbers, (x) => x * 2)
* const filtered = Iterable.filter(doubled, (x) => x > 5)
*
* console.log(Array.from(filtered)) // [6, 8, 10]
*
* // Infinite iterables
* const fibonacci = Iterable.unfold([0, 1], ([a, b]) => Option.some([a, [b, a + b]]))
* const first10 = Iterable.take(fibonacci, 10)
* console.log(Array.from(first10)) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
* ```
*
* @since 2.0.0
*/
import type { NonEmptyArray } from "./Array.ts"
import * as Equal from "./Equal.ts"
import { dual } from "./Function.ts"
import type { Option } from "./Option.ts"
import * as O from "./Option.ts"
import { isBoolean } from "./Predicate.ts"
import type * as Record from "./Record.ts"
import type { Result } from "./Result.ts"
import * as R from "./Result.ts"
import * as Tuple from "./Tuple.ts"
import type { NoInfer } from "./Types.ts"
/**
* Creates an iterable by applying a function to consecutive integers.
*
* This is a fundamental constructor that generates iterables by calling a function
* with each index starting from 0. If no length is specified, the iterable will
* be infinite. This is useful for generating sequences, patterns, or any indexed data.
*
* @param f - Function that receives the index and returns the element
* @param options - Configuration object with optional length
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Generate first 5 even numbers
* const evens = Iterable.makeBy((n) => n * 2, { length: 5 })
* console.log(Array.from(evens)) // [0, 2, 4, 6, 8]
*
* // Generate squares
* const squares = Iterable.makeBy((n) => n * n, { length: 4 })
* console.log(Array.from(squares)) // [0, 1, 4, 9]
*
* // Infinite sequence (be careful when consuming!)
* const naturals = Iterable.makeBy((n) => n)
* const first10 = Iterable.take(naturals, 10)
* console.log(Array.from(first10)) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* ```
*
* @category constructors
* @since 2.0.0
*/
export const makeBy = (f: (i: number) => A, options?: {
readonly length?: number
}): Iterable => {
const max = options?.length !== undefined ? Math.max(1, Math.floor(options.length)) : Infinity
return {
[Symbol.iterator]() {
let i = 0
return {
next(): IteratorResult {
if (i < max) {
return { value: f(i++), done: false }
}
return { done: true, value: undefined }
}
}
}
}
}
/**
* Return a `Iterable` containing a range of integers, including both endpoints.
*
* If `end` is omitted, the range will not have an upper bound.
*
* @example
* ```ts
* import { range } from "effect/Iterable"
* import * as assert from "node:assert"
*
* assert.deepStrictEqual(Array.from(range(1, 3)), [1, 2, 3])
* ```
*
* @category constructors
* @since 2.0.0
*/
export const range = (start: number, end?: number): Iterable => {
if (end === undefined) {
return makeBy((i) => start + i)
}
return makeBy((i) => start + i, {
length: start <= end ? end - start + 1 : 1
})
}
/**
* Return a `Iterable` containing a value repeated the specified number of times.
*
* **Note**. `n` is normalized to an integer >= 1.
*
* @example
* ```ts
* import { replicate } from "effect/Iterable"
* import * as assert from "node:assert"
*
* assert.deepStrictEqual(Array.from(replicate("a", 3)), ["a", "a", "a"])
* ```
*
* @category constructors
* @since 2.0.0
*/
export const replicate: {
/**
* Return a `Iterable` containing a value repeated the specified number of times.
*
* **Note**. `n` is normalized to an integer >= 1.
*
* @example
* ```ts
* import { replicate } from "effect/Iterable"
* import * as assert from "node:assert"
*
* assert.deepStrictEqual(Array.from(replicate("a", 3)), ["a", "a", "a"])
* ```
*
* @category constructors
* @since 2.0.0
*/
(n: number): (a: A) => Iterable
/**
* Return a `Iterable` containing a value repeated the specified number of times.
*
* **Note**. `n` is normalized to an integer >= 1.
*
* @example
* ```ts
* import { replicate } from "effect/Iterable"
* import * as assert from "node:assert"
*
* assert.deepStrictEqual(Array.from(replicate("a", 3)), ["a", "a", "a"])
* ```
*
* @category constructors
* @since 2.0.0
*/
(a: A, n: number): Iterable
} = dual(2, (a: A, n: number): Iterable => makeBy(() => a, { length: n }))
/**
* @category constructors
* @since 4.0.0
*/
export const repeat: {
/**
* @category constructors
* @since 4.0.0
*/
(n: number): (self: Iterable) => Iterable
/**
* @category constructors
* @since 4.0.0
*/
(self: Iterable, n: number): Iterable
} = dual(2, (self: Iterable, n: number): Iterable => flatten(makeBy(() => self, { length: n })))
/**
* @category constructors
* @since 4.0.0
*/
export const forever = (self: Iterable): Iterable => repeat(self, Infinity)
/**
* Takes a record and returns an Iterable of tuples containing its keys and values.
*
* @example
* ```ts
* import { fromRecord } from "effect/Iterable"
* import * as assert from "node:assert"
*
* const x = { a: 1, b: 2, c: 3 }
* assert.deepStrictEqual(Array.from(fromRecord(x)), [["a", 1], ["b", 2], [
* "c",
* 3
* ]])
* ```
*
* @category conversions
* @since 2.0.0
*/
export const fromRecord = (self: Readonly>): Iterable<[K, A]> => ({
*[Symbol.iterator]() {
for (const key in self) {
if (Object.hasOwn(self, key)) {
yield [key, self[key]]
}
}
}
})
/**
* Prepend an element to the front of an `Iterable`, creating a new `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [2, 3, 4]
* const withOne = Iterable.prepend(numbers, 1)
* console.log(Array.from(withOne)) // [1, 2, 3, 4]
*
* // Works with any iterable
* const letters = "abc"
* const withZ = Iterable.prepend(letters, "z")
* console.log(Array.from(withZ)) // ["z", "a", "b", "c"]
* ```
*
* @category concatenating
* @since 2.0.0
*/
export const prepend: {
/**
* Prepend an element to the front of an `Iterable`, creating a new `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [2, 3, 4]
* const withOne = Iterable.prepend(numbers, 1)
* console.log(Array.from(withOne)) // [1, 2, 3, 4]
*
* // Works with any iterable
* const letters = "abc"
* const withZ = Iterable.prepend(letters, "z")
* console.log(Array.from(withZ)) // ["z", "a", "b", "c"]
* ```
*
* @category concatenating
* @since 2.0.0
*/
(head: B): (self: Iterable) => Iterable
/**
* Prepend an element to the front of an `Iterable`, creating a new `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [2, 3, 4]
* const withOne = Iterable.prepend(numbers, 1)
* console.log(Array.from(withOne)) // [1, 2, 3, 4]
*
* // Works with any iterable
* const letters = "abc"
* const withZ = Iterable.prepend(letters, "z")
* console.log(Array.from(withZ)) // ["z", "a", "b", "c"]
* ```
*
* @category concatenating
* @since 2.0.0
*/
(self: Iterable, head: B): Iterable
} = dual(2, (self: Iterable, head: B): Iterable => prependAll(self, [head]))
/**
* Prepends the specified prefix iterable to the beginning of the specified iterable.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as assert from "node:assert"
*
* assert.deepStrictEqual(
* Array.from(Iterable.prependAll([1, 2], ["a", "b"])),
* ["a", "b", 1, 2]
* )
* ```
*
* @category concatenating
* @since 2.0.0
*/
export const prependAll: {
/**
* Prepends the specified prefix iterable to the beginning of the specified iterable.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as assert from "node:assert"
*
* assert.deepStrictEqual(
* Array.from(Iterable.prependAll([1, 2], ["a", "b"])),
* ["a", "b", 1, 2]
* )
* ```
*
* @category concatenating
* @since 2.0.0
*/
(that: Iterable): (self: Iterable) => Iterable
/**
* Prepends the specified prefix iterable to the beginning of the specified iterable.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as assert from "node:assert"
*
* assert.deepStrictEqual(
* Array.from(Iterable.prependAll([1, 2], ["a", "b"])),
* ["a", "b", 1, 2]
* )
* ```
*
* @category concatenating
* @since 2.0.0
*/
(self: Iterable, that: Iterable): Iterable
} = dual(
2,
(self: Iterable, that: Iterable): Iterable => appendAll(that, self)
)
/**
* Append an element to the end of an `Iterable`, creating a new `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3]
* const withFour = Iterable.append(numbers, 4)
* console.log(Array.from(withFour)) // [1, 2, 3, 4]
*
* // Chain multiple appends
* const result = Iterable.append(
* Iterable.append([1, 2], 3),
* 4
* )
* console.log(Array.from(result)) // [1, 2, 3, 4]
* ```
*
* @category concatenating
* @since 2.0.0
*/
export const append: {
/**
* Append an element to the end of an `Iterable`, creating a new `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3]
* const withFour = Iterable.append(numbers, 4)
* console.log(Array.from(withFour)) // [1, 2, 3, 4]
*
* // Chain multiple appends
* const result = Iterable.append(
* Iterable.append([1, 2], 3),
* 4
* )
* console.log(Array.from(result)) // [1, 2, 3, 4]
* ```
*
* @category concatenating
* @since 2.0.0
*/
(last: B): (self: Iterable) => Iterable
/**
* Append an element to the end of an `Iterable`, creating a new `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3]
* const withFour = Iterable.append(numbers, 4)
* console.log(Array.from(withFour)) // [1, 2, 3, 4]
*
* // Chain multiple appends
* const result = Iterable.append(
* Iterable.append([1, 2], 3),
* 4
* )
* console.log(Array.from(result)) // [1, 2, 3, 4]
* ```
*
* @category concatenating
* @since 2.0.0
*/
(self: Iterable, last: B): Iterable
} = dual(2, (self: Iterable, last: B): Iterable => appendAll(self, [last]))
/**
* Concatenates two iterables, combining their elements.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const first = [1, 2, 3]
* const second = [4, 5, 6]
* const combined = Iterable.appendAll(first, second)
* console.log(Array.from(combined)) // [1, 2, 3, 4, 5, 6]
*
* // Works with different iterable types
* const numbers = [1, 2]
* const letters = "abc"
* const mixed = Iterable.appendAll(numbers, letters)
* console.log(Array.from(mixed)) // [1, 2, "a", "b", "c"]
*
* // Lazy evaluation - only consumes what's needed
* const infinite = Iterable.range(1)
* const finite = [0, -1, -2]
* const result = Iterable.take(Iterable.appendAll(finite, infinite), 5)
* console.log(Array.from(result)) // [0, -1, -2, 1, 2]
* ```
*
* @category concatenating
* @since 2.0.0
*/
export const appendAll: {
/**
* Concatenates two iterables, combining their elements.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const first = [1, 2, 3]
* const second = [4, 5, 6]
* const combined = Iterable.appendAll(first, second)
* console.log(Array.from(combined)) // [1, 2, 3, 4, 5, 6]
*
* // Works with different iterable types
* const numbers = [1, 2]
* const letters = "abc"
* const mixed = Iterable.appendAll(numbers, letters)
* console.log(Array.from(mixed)) // [1, 2, "a", "b", "c"]
*
* // Lazy evaluation - only consumes what's needed
* const infinite = Iterable.range(1)
* const finite = [0, -1, -2]
* const result = Iterable.take(Iterable.appendAll(finite, infinite), 5)
* console.log(Array.from(result)) // [0, -1, -2, 1, 2]
* ```
*
* @category concatenating
* @since 2.0.0
*/
(that: Iterable): (self: Iterable) => Iterable
/**
* Concatenates two iterables, combining their elements.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const first = [1, 2, 3]
* const second = [4, 5, 6]
* const combined = Iterable.appendAll(first, second)
* console.log(Array.from(combined)) // [1, 2, 3, 4, 5, 6]
*
* // Works with different iterable types
* const numbers = [1, 2]
* const letters = "abc"
* const mixed = Iterable.appendAll(numbers, letters)
* console.log(Array.from(mixed)) // [1, 2, "a", "b", "c"]
*
* // Lazy evaluation - only consumes what's needed
* const infinite = Iterable.range(1)
* const finite = [0, -1, -2]
* const result = Iterable.take(Iterable.appendAll(finite, infinite), 5)
* console.log(Array.from(result)) // [0, -1, -2, 1, 2]
* ```
*
* @category concatenating
* @since 2.0.0
*/
(self: Iterable, that: Iterable): Iterable
} = dual(
2,
(self: Iterable, that: Iterable): Iterable => ({
[Symbol.iterator]() {
const iterA = self[Symbol.iterator]()
let doneA = false
let iterB: Iterator
return {
next() {
if (!doneA) {
const r = iterA.next()
if (r.done) {
doneA = true
iterB = that[Symbol.iterator]()
return iterB.next()
}
return r
}
return iterB.next()
}
}
}
})
)
/**
* Reduce an `Iterable` from the left, keeping all intermediate results instead of only the final result.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Running sum of numbers
* const numbers = [1, 2, 3, 4, 5]
* const runningSum = Iterable.scan(numbers, 0, (acc, n) => acc + n)
* console.log(Array.from(runningSum)) // [0, 1, 3, 6, 10, 15]
*
* // Build strings progressively
* const letters = ["a", "b", "c"]
* const progressive = Iterable.scan(letters, "", (acc, letter) => acc + letter)
* console.log(Array.from(progressive)) // ["", "a", "ab", "abc"]
*
* // Track maximum values seen so far
* const values = [3, 1, 4, 1, 5, 9, 2]
* const runningMax = Iterable.scan(values, -Infinity, Math.max)
* console.log(Array.from(runningMax)) // [-Infinity, 3, 3, 4, 4, 5, 9, 9]
* ```
*
* @category folding
* @since 2.0.0
*/
export const scan: {
/**
* Reduce an `Iterable` from the left, keeping all intermediate results instead of only the final result.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Running sum of numbers
* const numbers = [1, 2, 3, 4, 5]
* const runningSum = Iterable.scan(numbers, 0, (acc, n) => acc + n)
* console.log(Array.from(runningSum)) // [0, 1, 3, 6, 10, 15]
*
* // Build strings progressively
* const letters = ["a", "b", "c"]
* const progressive = Iterable.scan(letters, "", (acc, letter) => acc + letter)
* console.log(Array.from(progressive)) // ["", "a", "ab", "abc"]
*
* // Track maximum values seen so far
* const values = [3, 1, 4, 1, 5, 9, 2]
* const runningMax = Iterable.scan(values, -Infinity, Math.max)
* console.log(Array.from(runningMax)) // [-Infinity, 3, 3, 4, 4, 5, 9, 9]
* ```
*
* @category folding
* @since 2.0.0
*/
(b: B, f: (b: B, a: A) => B): (self: Iterable) => Iterable
/**
* Reduce an `Iterable` from the left, keeping all intermediate results instead of only the final result.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Running sum of numbers
* const numbers = [1, 2, 3, 4, 5]
* const runningSum = Iterable.scan(numbers, 0, (acc, n) => acc + n)
* console.log(Array.from(runningSum)) // [0, 1, 3, 6, 10, 15]
*
* // Build strings progressively
* const letters = ["a", "b", "c"]
* const progressive = Iterable.scan(letters, "", (acc, letter) => acc + letter)
* console.log(Array.from(progressive)) // ["", "a", "ab", "abc"]
*
* // Track maximum values seen so far
* const values = [3, 1, 4, 1, 5, 9, 2]
* const runningMax = Iterable.scan(values, -Infinity, Math.max)
* console.log(Array.from(runningMax)) // [-Infinity, 3, 3, 4, 4, 5, 9, 9]
* ```
*
* @category folding
* @since 2.0.0
*/
(self: Iterable, b: B, f: (b: B, a: A) => B): Iterable
} = dual(3, (self: Iterable, b: B, f: (b: B, a: A) => B): Iterable => ({
[Symbol.iterator]() {
let acc = b
let iterator: Iterator | undefined
function next() {
if (iterator === undefined) {
iterator = self[Symbol.iterator]()
return { done: false, value: acc }
}
const result = iterator.next()
if (result.done) {
return result
}
acc = f(acc, result.value)
return { done: false, value: acc }
}
return { next }
}
}))
/**
* Determine if an `Iterable` is empty
*
* @example
* ```ts
* import { isEmpty } from "effect/Iterable"
* import * as assert from "node:assert"
*
* assert.deepStrictEqual(isEmpty([]), true)
* assert.deepStrictEqual(isEmpty([1, 2, 3]), false)
* ```
*
* @category guards
* @since 2.0.0
*/
export const isEmpty = (self: Iterable): self is Iterable => {
const iterator = self[Symbol.iterator]()
return iterator.next().done === true
}
/**
* Return the number of elements in a `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5]
* console.log(Iterable.size(numbers)) // 5
*
* const empty = Iterable.empty()
* console.log(Iterable.size(empty)) // 0
*
* // Works with any iterable
* const letters = "hello"
* console.log(Iterable.size(letters)) // 5
*
* // Note: This consumes the entire iterable
* const range = Iterable.range(1, 100)
* console.log(Iterable.size(range)) // 100
* ```
*
* @category getters
* @since 2.0.0
*/
export const size = (self: Iterable): number => {
const iterator = self[Symbol.iterator]()
let count = 0
while (!iterator.next().done) {
count++
}
return count
}
/**
* Get the first element of a `Iterable`, or `None` if the `Iterable` is empty.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Option from "effect/Option"
*
* const numbers = [1, 2, 3]
* console.log(Iterable.head(numbers)) // Option.some(1)
*
* const empty = Iterable.empty()
* console.log(Iterable.head(empty)) // Option.none()
*
* // Safe way to get first element
* const firstEven = Iterable.head(
* Iterable.filter([1, 3, 4, 5], (x) => x % 2 === 0)
* )
* console.log(firstEven) // Option.some(4)
*
* // Use with Option methods
* const doubled = Option.map(Iterable.head([5, 10, 15]), (x) => x * 2)
* console.log(doubled) // Option.some(10)
* ```
*
* @category getters
* @since 2.0.0
*/
export const head = (self: Iterable): Option => {
const iterator = self[Symbol.iterator]()
const result = iterator.next()
return result.done ? O.none() : O.some(result.value)
}
/**
* Get the first element of a `Iterable`, or throw an error if the `Iterable` is empty.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3]
* console.log(Iterable.headUnsafe(numbers)) // 1
*
* const letters = "hello"
* console.log(Iterable.headUnsafe(letters)) // "h"
*
* // This will throw an error!
* try {
* const empty = Iterable.empty()
* Iterable.headUnsafe(empty) // throws Error: "headUnsafe: empty iterable"
* } catch (error) {
* console.log((error as Error).message) // "headUnsafe: empty iterable"
* }
*
* // Use only when you're certain the iterable is non-empty
* const nonEmpty = Iterable.range(1, 10)
* console.log(Iterable.headUnsafe(nonEmpty)) // 1
* ```
*
* @category getters
* @since 3.3.0
*/
export const headUnsafe = (self: Iterable): A => {
const iterator = self[Symbol.iterator]()
const result = iterator.next()
if (result.done) throw new Error("headUnsafe: empty iterable")
return result.value
}
/**
* Keep only a max number of elements from the start of an `Iterable`, creating a new `Iterable`.
*
* **Note**. `n` is normalized to a non negative integer.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5]
* const firstThree = Iterable.take(numbers, 3)
* console.log(Array.from(firstThree)) // [1, 2, 3]
*
* // Taking more than available returns all elements
* const firstTen = Iterable.take(numbers, 10)
* console.log(Array.from(firstTen)) // [1, 2, 3, 4, 5]
*
* // Taking 0 or negative returns empty
* const none = Iterable.take(numbers, 0)
* console.log(Array.from(none)) // []
*
* // Useful with infinite iterables
* const naturals = Iterable.range(1)
* const firstFive = Iterable.take(naturals, 5)
* console.log(Array.from(firstFive)) // [1, 2, 3, 4, 5]
* ```
*
* @category getters
* @since 2.0.0
*/
export const take: {
/**
* Keep only a max number of elements from the start of an `Iterable`, creating a new `Iterable`.
*
* **Note**. `n` is normalized to a non negative integer.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5]
* const firstThree = Iterable.take(numbers, 3)
* console.log(Array.from(firstThree)) // [1, 2, 3]
*
* // Taking more than available returns all elements
* const firstTen = Iterable.take(numbers, 10)
* console.log(Array.from(firstTen)) // [1, 2, 3, 4, 5]
*
* // Taking 0 or negative returns empty
* const none = Iterable.take(numbers, 0)
* console.log(Array.from(none)) // []
*
* // Useful with infinite iterables
* const naturals = Iterable.range(1)
* const firstFive = Iterable.take(naturals, 5)
* console.log(Array.from(firstFive)) // [1, 2, 3, 4, 5]
* ```
*
* @category getters
* @since 2.0.0
*/
(n: number): (self: Iterable) => Iterable
/**
* Keep only a max number of elements from the start of an `Iterable`, creating a new `Iterable`.
*
* **Note**. `n` is normalized to a non negative integer.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5]
* const firstThree = Iterable.take(numbers, 3)
* console.log(Array.from(firstThree)) // [1, 2, 3]
*
* // Taking more than available returns all elements
* const firstTen = Iterable.take(numbers, 10)
* console.log(Array.from(firstTen)) // [1, 2, 3, 4, 5]
*
* // Taking 0 or negative returns empty
* const none = Iterable.take(numbers, 0)
* console.log(Array.from(none)) // []
*
* // Useful with infinite iterables
* const naturals = Iterable.range(1)
* const firstFive = Iterable.take(naturals, 5)
* console.log(Array.from(firstFive)) // [1, 2, 3, 4, 5]
* ```
*
* @category getters
* @since 2.0.0
*/
(self: Iterable, n: number): Iterable
} = dual(2, (self: Iterable, n: number): Iterable => ({
[Symbol.iterator]() {
let i = 0
const iterator = self[Symbol.iterator]()
return {
next() {
if (i < n) {
i++
return iterator.next()
}
return { done: true, value: undefined }
}
}
}
}))
/**
* Calculate the longest initial Iterable for which all element satisfy the specified predicate, creating a new `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [2, 4, 6, 8, 3, 10, 12]
* const evenPrefix = Iterable.takeWhile(numbers, (x) => x % 2 === 0)
* console.log(Array.from(evenPrefix)) // [2, 4, 6, 8]
*
* // With index
* const letters = ["a", "b", "c", "d", "e"]
* const firstThreeByIndex = Iterable.takeWhile(letters, (_, i) => i < 3)
* console.log(Array.from(firstThreeByIndex)) // ["a", "b", "c"]
*
* // Stops at first non-matching element
* const mixed = [1, 3, 5, 4, 7, 9]
* const oddPrefix = Iterable.takeWhile(mixed, (x) => x % 2 === 1)
* console.log(Array.from(oddPrefix)) // [1, 3, 5]
*
* // Type refinement
* const values: Array = ["a", "b", "c", 1, "d"]
* const stringPrefix = Iterable.takeWhile(
* values,
* (x): x is string => typeof x === "string"
* )
* console.log(Array.from(stringPrefix)) // ["a", "b", "c"] (typed as string[])
* ```
*
* @category getters
* @since 2.0.0
*/
export const takeWhile: {
/**
* Calculate the longest initial Iterable for which all element satisfy the specified predicate, creating a new `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [2, 4, 6, 8, 3, 10, 12]
* const evenPrefix = Iterable.takeWhile(numbers, (x) => x % 2 === 0)
* console.log(Array.from(evenPrefix)) // [2, 4, 6, 8]
*
* // With index
* const letters = ["a", "b", "c", "d", "e"]
* const firstThreeByIndex = Iterable.takeWhile(letters, (_, i) => i < 3)
* console.log(Array.from(firstThreeByIndex)) // ["a", "b", "c"]
*
* // Stops at first non-matching element
* const mixed = [1, 3, 5, 4, 7, 9]
* const oddPrefix = Iterable.takeWhile(mixed, (x) => x % 2 === 1)
* console.log(Array.from(oddPrefix)) // [1, 3, 5]
*
* // Type refinement
* const values: Array = ["a", "b", "c", 1, "d"]
* const stringPrefix = Iterable.takeWhile(
* values,
* (x): x is string => typeof x === "string"
* )
* console.log(Array.from(stringPrefix)) // ["a", "b", "c"] (typed as string[])
* ```
*
* @category getters
* @since 2.0.0
*/
(refinement: (a: NoInfer, i: number) => a is B): (self: Iterable) => Iterable
/**
* Calculate the longest initial Iterable for which all element satisfy the specified predicate, creating a new `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [2, 4, 6, 8, 3, 10, 12]
* const evenPrefix = Iterable.takeWhile(numbers, (x) => x % 2 === 0)
* console.log(Array.from(evenPrefix)) // [2, 4, 6, 8]
*
* // With index
* const letters = ["a", "b", "c", "d", "e"]
* const firstThreeByIndex = Iterable.takeWhile(letters, (_, i) => i < 3)
* console.log(Array.from(firstThreeByIndex)) // ["a", "b", "c"]
*
* // Stops at first non-matching element
* const mixed = [1, 3, 5, 4, 7, 9]
* const oddPrefix = Iterable.takeWhile(mixed, (x) => x % 2 === 1)
* console.log(Array.from(oddPrefix)) // [1, 3, 5]
*
* // Type refinement
* const values: Array = ["a", "b", "c", 1, "d"]
* const stringPrefix = Iterable.takeWhile(
* values,
* (x): x is string => typeof x === "string"
* )
* console.log(Array.from(stringPrefix)) // ["a", "b", "c"] (typed as string[])
* ```
*
* @category getters
* @since 2.0.0
*/
(predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Iterable
/**
* Calculate the longest initial Iterable for which all element satisfy the specified predicate, creating a new `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [2, 4, 6, 8, 3, 10, 12]
* const evenPrefix = Iterable.takeWhile(numbers, (x) => x % 2 === 0)
* console.log(Array.from(evenPrefix)) // [2, 4, 6, 8]
*
* // With index
* const letters = ["a", "b", "c", "d", "e"]
* const firstThreeByIndex = Iterable.takeWhile(letters, (_, i) => i < 3)
* console.log(Array.from(firstThreeByIndex)) // ["a", "b", "c"]
*
* // Stops at first non-matching element
* const mixed = [1, 3, 5, 4, 7, 9]
* const oddPrefix = Iterable.takeWhile(mixed, (x) => x % 2 === 1)
* console.log(Array.from(oddPrefix)) // [1, 3, 5]
*
* // Type refinement
* const values: Array = ["a", "b", "c", 1, "d"]
* const stringPrefix = Iterable.takeWhile(
* values,
* (x): x is string => typeof x === "string"
* )
* console.log(Array.from(stringPrefix)) // ["a", "b", "c"] (typed as string[])
* ```
*
* @category getters
* @since 2.0.0
*/
(self: Iterable, refinement: (a: A, i: number) => a is B): Iterable
/**
* Calculate the longest initial Iterable for which all element satisfy the specified predicate, creating a new `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [2, 4, 6, 8, 3, 10, 12]
* const evenPrefix = Iterable.takeWhile(numbers, (x) => x % 2 === 0)
* console.log(Array.from(evenPrefix)) // [2, 4, 6, 8]
*
* // With index
* const letters = ["a", "b", "c", "d", "e"]
* const firstThreeByIndex = Iterable.takeWhile(letters, (_, i) => i < 3)
* console.log(Array.from(firstThreeByIndex)) // ["a", "b", "c"]
*
* // Stops at first non-matching element
* const mixed = [1, 3, 5, 4, 7, 9]
* const oddPrefix = Iterable.takeWhile(mixed, (x) => x % 2 === 1)
* console.log(Array.from(oddPrefix)) // [1, 3, 5]
*
* // Type refinement
* const values: Array = ["a", "b", "c", 1, "d"]
* const stringPrefix = Iterable.takeWhile(
* values,
* (x): x is string => typeof x === "string"
* )
* console.log(Array.from(stringPrefix)) // ["a", "b", "c"] (typed as string[])
* ```
*
* @category getters
* @since 2.0.0
*/
(self: Iterable, predicate: (a: A, i: number) => boolean): Iterable
} = dual(2, (self: Iterable, predicate: (a: A, i: number) => boolean): Iterable => ({
[Symbol.iterator]() {
const iterator = self[Symbol.iterator]()
let i = 0
return {
next() {
const result = iterator.next()
if (result.done || !predicate(result.value, i++)) {
return { done: true, value: undefined }
}
return result
}
}
}
}))
/**
* Drop a max number of elements from the start of an `Iterable`
*
* **Note**. `n` is normalized to a non negative integer.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5]
* const withoutFirstTwo = Iterable.drop(numbers, 2)
* console.log(Array.from(withoutFirstTwo)) // [3, 4, 5]
*
* // Dropping more than available returns empty
* const withoutFirstTen = Iterable.drop(numbers, 10)
* console.log(Array.from(withoutFirstTen)) // []
*
* // Dropping 0 or negative returns all elements
* const all = Iterable.drop(numbers, 0)
* console.log(Array.from(all)) // [1, 2, 3, 4, 5]
*
* // Combine with take for slicing
* const slice = Iterable.take(Iterable.drop(numbers, 1), 3)
* console.log(Array.from(slice)) // [2, 3, 4]
* ```
*
* @category getters
* @since 2.0.0
*/
export const drop: {
/**
* Drop a max number of elements from the start of an `Iterable`
*
* **Note**. `n` is normalized to a non negative integer.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5]
* const withoutFirstTwo = Iterable.drop(numbers, 2)
* console.log(Array.from(withoutFirstTwo)) // [3, 4, 5]
*
* // Dropping more than available returns empty
* const withoutFirstTen = Iterable.drop(numbers, 10)
* console.log(Array.from(withoutFirstTen)) // []
*
* // Dropping 0 or negative returns all elements
* const all = Iterable.drop(numbers, 0)
* console.log(Array.from(all)) // [1, 2, 3, 4, 5]
*
* // Combine with take for slicing
* const slice = Iterable.take(Iterable.drop(numbers, 1), 3)
* console.log(Array.from(slice)) // [2, 3, 4]
* ```
*
* @category getters
* @since 2.0.0
*/
(n: number): (self: Iterable) => Iterable
/**
* Drop a max number of elements from the start of an `Iterable`
*
* **Note**. `n` is normalized to a non negative integer.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5]
* const withoutFirstTwo = Iterable.drop(numbers, 2)
* console.log(Array.from(withoutFirstTwo)) // [3, 4, 5]
*
* // Dropping more than available returns empty
* const withoutFirstTen = Iterable.drop(numbers, 10)
* console.log(Array.from(withoutFirstTen)) // []
*
* // Dropping 0 or negative returns all elements
* const all = Iterable.drop(numbers, 0)
* console.log(Array.from(all)) // [1, 2, 3, 4, 5]
*
* // Combine with take for slicing
* const slice = Iterable.take(Iterable.drop(numbers, 1), 3)
* console.log(Array.from(slice)) // [2, 3, 4]
* ```
*
* @category getters
* @since 2.0.0
*/
(self: Iterable, n: number): Iterable
} = dual(2, (self: Iterable, n: number): Iterable => ({
[Symbol.iterator]() {
const iterator = self[Symbol.iterator]()
let i = 0
return {
next() {
while (i < n) {
const result = iterator.next()
if (result.done) {
return { done: true, value: undefined }
}
i++
}
return iterator.next()
}
}
}
}))
/**
* Returns the first element that satisfies the specified
* predicate, or `None` if no such element exists.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Option from "effect/Option"
*
* const numbers = [1, 3, 4, 6, 8]
* const firstEven = Iterable.findFirst(numbers, (x) => x % 2 === 0)
* console.log(firstEven) // Option.some(4)
*
* const firstGreaterThan10 = Iterable.findFirst(numbers, (x) => x > 10)
* console.log(firstGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d"]
* const atEvenIndex = Iterable.findFirst(letters, (_, i) => i % 2 === 0)
* console.log(atEvenIndex) // Option.some("a")
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world"]
* const firstString = Iterable.findFirst(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(firstString) // Option.some("hello")
*
* // Transform during search
* const findSquareRoot = Iterable.findFirst([1, 4, 9, 16], (x) => {
* const sqrt = Math.sqrt(x)
* return Number.isInteger(sqrt) ? Option.some(sqrt) : Option.none()
* })
* console.log(findSquareRoot) // Option.some(1)
* ```
*
* @category elements
* @since 2.0.0
*/
export const findFirst: {
/**
* Returns the first element that satisfies the specified
* predicate, or `None` if no such element exists.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Option from "effect/Option"
*
* const numbers = [1, 3, 4, 6, 8]
* const firstEven = Iterable.findFirst(numbers, (x) => x % 2 === 0)
* console.log(firstEven) // Option.some(4)
*
* const firstGreaterThan10 = Iterable.findFirst(numbers, (x) => x > 10)
* console.log(firstGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d"]
* const atEvenIndex = Iterable.findFirst(letters, (_, i) => i % 2 === 0)
* console.log(atEvenIndex) // Option.some("a")
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world"]
* const firstString = Iterable.findFirst(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(firstString) // Option.some("hello")
*
* // Transform during search
* const findSquareRoot = Iterable.findFirst([1, 4, 9, 16], (x) => {
* const sqrt = Math.sqrt(x)
* return Number.isInteger(sqrt) ? Option.some(sqrt) : Option.none()
* })
* console.log(findSquareRoot) // Option.some(1)
* ```
*
* @category elements
* @since 2.0.0
*/
(f: (a: NoInfer, i: number) => Option): (self: Iterable) => Option
/**
* Returns the first element that satisfies the specified
* predicate, or `None` if no such element exists.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Option from "effect/Option"
*
* const numbers = [1, 3, 4, 6, 8]
* const firstEven = Iterable.findFirst(numbers, (x) => x % 2 === 0)
* console.log(firstEven) // Option.some(4)
*
* const firstGreaterThan10 = Iterable.findFirst(numbers, (x) => x > 10)
* console.log(firstGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d"]
* const atEvenIndex = Iterable.findFirst(letters, (_, i) => i % 2 === 0)
* console.log(atEvenIndex) // Option.some("a")
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world"]
* const firstString = Iterable.findFirst(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(firstString) // Option.some("hello")
*
* // Transform during search
* const findSquareRoot = Iterable.findFirst([1, 4, 9, 16], (x) => {
* const sqrt = Math.sqrt(x)
* return Number.isInteger(sqrt) ? Option.some(sqrt) : Option.none()
* })
* console.log(findSquareRoot) // Option.some(1)
* ```
*
* @category elements
* @since 2.0.0
*/
(refinement: (a: NoInfer, i: number) => a is B): (self: Iterable) => Option
/**
* Returns the first element that satisfies the specified
* predicate, or `None` if no such element exists.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Option from "effect/Option"
*
* const numbers = [1, 3, 4, 6, 8]
* const firstEven = Iterable.findFirst(numbers, (x) => x % 2 === 0)
* console.log(firstEven) // Option.some(4)
*
* const firstGreaterThan10 = Iterable.findFirst(numbers, (x) => x > 10)
* console.log(firstGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d"]
* const atEvenIndex = Iterable.findFirst(letters, (_, i) => i % 2 === 0)
* console.log(atEvenIndex) // Option.some("a")
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world"]
* const firstString = Iterable.findFirst(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(firstString) // Option.some("hello")
*
* // Transform during search
* const findSquareRoot = Iterable.findFirst([1, 4, 9, 16], (x) => {
* const sqrt = Math.sqrt(x)
* return Number.isInteger(sqrt) ? Option.some(sqrt) : Option.none()
* })
* console.log(findSquareRoot) // Option.some(1)
* ```
*
* @category elements
* @since 2.0.0
*/
(predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Option
/**
* Returns the first element that satisfies the specified
* predicate, or `None` if no such element exists.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Option from "effect/Option"
*
* const numbers = [1, 3, 4, 6, 8]
* const firstEven = Iterable.findFirst(numbers, (x) => x % 2 === 0)
* console.log(firstEven) // Option.some(4)
*
* const firstGreaterThan10 = Iterable.findFirst(numbers, (x) => x > 10)
* console.log(firstGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d"]
* const atEvenIndex = Iterable.findFirst(letters, (_, i) => i % 2 === 0)
* console.log(atEvenIndex) // Option.some("a")
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world"]
* const firstString = Iterable.findFirst(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(firstString) // Option.some("hello")
*
* // Transform during search
* const findSquareRoot = Iterable.findFirst([1, 4, 9, 16], (x) => {
* const sqrt = Math.sqrt(x)
* return Number.isInteger(sqrt) ? Option.some(sqrt) : Option.none()
* })
* console.log(findSquareRoot) // Option.some(1)
* ```
*
* @category elements
* @since 2.0.0
*/
(self: Iterable, f: (a: A, i: number) => Option): Option
/**
* Returns the first element that satisfies the specified
* predicate, or `None` if no such element exists.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Option from "effect/Option"
*
* const numbers = [1, 3, 4, 6, 8]
* const firstEven = Iterable.findFirst(numbers, (x) => x % 2 === 0)
* console.log(firstEven) // Option.some(4)
*
* const firstGreaterThan10 = Iterable.findFirst(numbers, (x) => x > 10)
* console.log(firstGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d"]
* const atEvenIndex = Iterable.findFirst(letters, (_, i) => i % 2 === 0)
* console.log(atEvenIndex) // Option.some("a")
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world"]
* const firstString = Iterable.findFirst(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(firstString) // Option.some("hello")
*
* // Transform during search
* const findSquareRoot = Iterable.findFirst([1, 4, 9, 16], (x) => {
* const sqrt = Math.sqrt(x)
* return Number.isInteger(sqrt) ? Option.some(sqrt) : Option.none()
* })
* console.log(findSquareRoot) // Option.some(1)
* ```
*
* @category elements
* @since 2.0.0
*/
(self: Iterable, refinement: (a: A, i: number) => a is B): Option
/**
* Returns the first element that satisfies the specified
* predicate, or `None` if no such element exists.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Option from "effect/Option"
*
* const numbers = [1, 3, 4, 6, 8]
* const firstEven = Iterable.findFirst(numbers, (x) => x % 2 === 0)
* console.log(firstEven) // Option.some(4)
*
* const firstGreaterThan10 = Iterable.findFirst(numbers, (x) => x > 10)
* console.log(firstGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d"]
* const atEvenIndex = Iterable.findFirst(letters, (_, i) => i % 2 === 0)
* console.log(atEvenIndex) // Option.some("a")
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world"]
* const firstString = Iterable.findFirst(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(firstString) // Option.some("hello")
*
* // Transform during search
* const findSquareRoot = Iterable.findFirst([1, 4, 9, 16], (x) => {
* const sqrt = Math.sqrt(x)
* return Number.isInteger(sqrt) ? Option.some(sqrt) : Option.none()
* })
* console.log(findSquareRoot) // Option.some(1)
* ```
*
* @category elements
* @since 2.0.0
*/
(self: Iterable, predicate: (a: A, i: number) => boolean): Option
} = dual(
2,
(self: Iterable, f: ((a: A, i: number) => boolean) | ((a: A, i: number) => Option)): Option => {
let i = 0
for (const a of self) {
const o = f(a, i)
if (isBoolean(o)) {
if (o) {
return O.some(a)
}
} else {
if (O.isSome(o)) {
return o
}
}
i++
}
return O.none()
}
)
/**
* Find the last element for which a predicate holds.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 3, 4, 6, 8, 2]
* const lastEven = Iterable.findLast(numbers, (x) => x % 2 === 0)
* console.log(lastEven) // Option.some(2)
*
* const lastGreaterThan10 = Iterable.findLast(numbers, (x) => x > 10)
* console.log(lastGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d", "e"]
* const lastAtEvenIndex = Iterable.findLast(letters, (_, i) => i % 2 === 0)
* console.log(lastAtEvenIndex) // Option.some("e") (index 4)
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world", 3]
* const lastString = Iterable.findLast(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(lastString) // Option.some("world")
* ```
*
* @category elements
* @since 2.0.0
*/
export const findLast: {
/**
* Find the last element for which a predicate holds.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 3, 4, 6, 8, 2]
* const lastEven = Iterable.findLast(numbers, (x) => x % 2 === 0)
* console.log(lastEven) // Option.some(2)
*
* const lastGreaterThan10 = Iterable.findLast(numbers, (x) => x > 10)
* console.log(lastGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d", "e"]
* const lastAtEvenIndex = Iterable.findLast(letters, (_, i) => i % 2 === 0)
* console.log(lastAtEvenIndex) // Option.some("e") (index 4)
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world", 3]
* const lastString = Iterable.findLast(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(lastString) // Option.some("world")
* ```
*
* @category elements
* @since 2.0.0
*/
(f: (a: NoInfer, i: number) => Option): (self: Iterable) => Option
/**
* Find the last element for which a predicate holds.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 3, 4, 6, 8, 2]
* const lastEven = Iterable.findLast(numbers, (x) => x % 2 === 0)
* console.log(lastEven) // Option.some(2)
*
* const lastGreaterThan10 = Iterable.findLast(numbers, (x) => x > 10)
* console.log(lastGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d", "e"]
* const lastAtEvenIndex = Iterable.findLast(letters, (_, i) => i % 2 === 0)
* console.log(lastAtEvenIndex) // Option.some("e") (index 4)
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world", 3]
* const lastString = Iterable.findLast(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(lastString) // Option.some("world")
* ```
*
* @category elements
* @since 2.0.0
*/
(refinement: (a: NoInfer, i: number) => a is B): (self: Iterable) => Option
/**
* Find the last element for which a predicate holds.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 3, 4, 6, 8, 2]
* const lastEven = Iterable.findLast(numbers, (x) => x % 2 === 0)
* console.log(lastEven) // Option.some(2)
*
* const lastGreaterThan10 = Iterable.findLast(numbers, (x) => x > 10)
* console.log(lastGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d", "e"]
* const lastAtEvenIndex = Iterable.findLast(letters, (_, i) => i % 2 === 0)
* console.log(lastAtEvenIndex) // Option.some("e") (index 4)
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world", 3]
* const lastString = Iterable.findLast(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(lastString) // Option.some("world")
* ```
*
* @category elements
* @since 2.0.0
*/
(predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Option
/**
* Find the last element for which a predicate holds.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 3, 4, 6, 8, 2]
* const lastEven = Iterable.findLast(numbers, (x) => x % 2 === 0)
* console.log(lastEven) // Option.some(2)
*
* const lastGreaterThan10 = Iterable.findLast(numbers, (x) => x > 10)
* console.log(lastGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d", "e"]
* const lastAtEvenIndex = Iterable.findLast(letters, (_, i) => i % 2 === 0)
* console.log(lastAtEvenIndex) // Option.some("e") (index 4)
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world", 3]
* const lastString = Iterable.findLast(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(lastString) // Option.some("world")
* ```
*
* @category elements
* @since 2.0.0
*/
(self: Iterable, f: (a: A, i: number) => Option): Option
/**
* Find the last element for which a predicate holds.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 3, 4, 6, 8, 2]
* const lastEven = Iterable.findLast(numbers, (x) => x % 2 === 0)
* console.log(lastEven) // Option.some(2)
*
* const lastGreaterThan10 = Iterable.findLast(numbers, (x) => x > 10)
* console.log(lastGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d", "e"]
* const lastAtEvenIndex = Iterable.findLast(letters, (_, i) => i % 2 === 0)
* console.log(lastAtEvenIndex) // Option.some("e") (index 4)
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world", 3]
* const lastString = Iterable.findLast(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(lastString) // Option.some("world")
* ```
*
* @category elements
* @since 2.0.0
*/
(self: Iterable, refinement: (a: A, i: number) => a is B): Option
/**
* Find the last element for which a predicate holds.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 3, 4, 6, 8, 2]
* const lastEven = Iterable.findLast(numbers, (x) => x % 2 === 0)
* console.log(lastEven) // Option.some(2)
*
* const lastGreaterThan10 = Iterable.findLast(numbers, (x) => x > 10)
* console.log(lastGreaterThan10) // Option.none()
*
* // With index
* const letters = ["a", "b", "c", "d", "e"]
* const lastAtEvenIndex = Iterable.findLast(letters, (_, i) => i % 2 === 0)
* console.log(lastAtEvenIndex) // Option.some("e") (index 4)
*
* // Type refinement
* const mixed: Array = [1, "hello", 2, "world", 3]
* const lastString = Iterable.findLast(
* mixed,
* (x): x is string => typeof x === "string"
* )
* console.log(lastString) // Option.some("world")
* ```
*
* @category elements
* @since 2.0.0
*/
(self: Iterable, predicate: (a: A, i: number) => boolean): Option
} = dual(
2,
(self: Iterable, f: ((a: A, i: number) => boolean) | ((a: A, i: number) => Option)): Option => {
let i = 0
let last: Option = O.none()
for (const a of self) {
const o = f(a, i)
if (isBoolean(o)) {
if (o) {
last = O.some(a)
}
} else {
if (O.isSome(o)) {
last = o
}
}
i++
}
return last
}
)
/**
* Takes two `Iterable`s and returns an `Iterable` of corresponding pairs.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3]
* const letters = ["a", "b", "c"]
* const zipped = Iterable.zip(numbers, letters)
* console.log(Array.from(zipped)) // [[1, "a"], [2, "b"], [3, "c"]]
*
* // Different lengths - shorter one determines result length
* const short = [1, 2]
* const long = ["a", "b", "c", "d"]
* const partial = Iterable.zip(short, long)
* console.log(Array.from(partial)) // [[1, "a"], [2, "b"]]
*
* // Works with any iterables
* const range = Iterable.range(1, 3)
* const word = "abc"
* const mixed = Iterable.zip(range, word)
* console.log(Array.from(mixed)) // [[1, "a"], [2, "b"], [3, "c"]]
*
* // Create indexed pairs
* const values = ["apple", "banana", "cherry"]
* const indices = Iterable.range(0, 2)
* const indexed = Iterable.zip(indices, values)
* console.log(Array.from(indexed)) // [[0, "apple"], [1, "banana"], [2, "cherry"]]
* ```
*
* @category zipping
* @since 2.0.0
*/
export const zip: {
/**
* Takes two `Iterable`s and returns an `Iterable` of corresponding pairs.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3]
* const letters = ["a", "b", "c"]
* const zipped = Iterable.zip(numbers, letters)
* console.log(Array.from(zipped)) // [[1, "a"], [2, "b"], [3, "c"]]
*
* // Different lengths - shorter one determines result length
* const short = [1, 2]
* const long = ["a", "b", "c", "d"]
* const partial = Iterable.zip(short, long)
* console.log(Array.from(partial)) // [[1, "a"], [2, "b"]]
*
* // Works with any iterables
* const range = Iterable.range(1, 3)
* const word = "abc"
* const mixed = Iterable.zip(range, word)
* console.log(Array.from(mixed)) // [[1, "a"], [2, "b"], [3, "c"]]
*
* // Create indexed pairs
* const values = ["apple", "banana", "cherry"]
* const indices = Iterable.range(0, 2)
* const indexed = Iterable.zip(indices, values)
* console.log(Array.from(indexed)) // [[0, "apple"], [1, "banana"], [2, "cherry"]]
* ```
*
* @category zipping
* @since 2.0.0
*/
(that: Iterable): (self: Iterable) => Iterable<[A, B]>
/**
* Takes two `Iterable`s and returns an `Iterable` of corresponding pairs.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3]
* const letters = ["a", "b", "c"]
* const zipped = Iterable.zip(numbers, letters)
* console.log(Array.from(zipped)) // [[1, "a"], [2, "b"], [3, "c"]]
*
* // Different lengths - shorter one determines result length
* const short = [1, 2]
* const long = ["a", "b", "c", "d"]
* const partial = Iterable.zip(short, long)
* console.log(Array.from(partial)) // [[1, "a"], [2, "b"]]
*
* // Works with any iterables
* const range = Iterable.range(1, 3)
* const word = "abc"
* const mixed = Iterable.zip(range, word)
* console.log(Array.from(mixed)) // [[1, "a"], [2, "b"], [3, "c"]]
*
* // Create indexed pairs
* const values = ["apple", "banana", "cherry"]
* const indices = Iterable.range(0, 2)
* const indexed = Iterable.zip(indices, values)
* console.log(Array.from(indexed)) // [[0, "apple"], [1, "banana"], [2, "cherry"]]
* ```
*
* @category zipping
* @since 2.0.0
*/
(self: Iterable, that: Iterable): Iterable<[A, B]>
} = dual(
2,
(self: Iterable, that: Iterable): Iterable<[A, B]> => zipWith(self, that, Tuple.make)
)
/**
* Apply a function to pairs of elements at the same index in two `Iterable`s, collecting the results. If one
* input `Iterable` is short, excess elements of the longer `Iterable` are discarded.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Add corresponding elements
* const a = [1, 2, 3, 4]
* const b = [10, 20, 30, 40]
* const sums = Iterable.zipWith(a, b, (x, y) => x + y)
* console.log(Array.from(sums)) // [11, 22, 33, 44]
*
* // Combine strings
* const firstNames = ["John", "Jane", "Bob"]
* const lastNames = ["Doe", "Smith", "Johnson"]
* const fullNames = Iterable.zipWith(
* firstNames,
* lastNames,
* (first, last) => `${first} ${last}`
* )
* console.log(Array.from(fullNames)) // ["John Doe", "Jane Smith", "Bob Johnson"]
*
* // Different lengths - stops at shorter
* const short = [1, 2]
* const long = ["a", "b", "c", "d"]
* const combined = Iterable.zipWith(
* short,
* long,
* (num, letter) => `${num}${letter}`
* )
* console.log(Array.from(combined)) // ["1a", "2b"]
*
* // Complex transformations
* const prices = [10.99, 25.50, 5.00]
* const quantities = [2, 1, 3]
* const totals = Iterable.zipWith(prices, quantities, (price, qty) => {
* return Math.round(price * qty * 100) / 100 // round to 2 decimal places
* })
* console.log(Array.from(totals)) // [21.98, 25.5, 15]
* ```
*
* @category zipping
* @since 2.0.0
*/
export const zipWith: {
/**
* Apply a function to pairs of elements at the same index in two `Iterable`s, collecting the results. If one
* input `Iterable` is short, excess elements of the longer `Iterable` are discarded.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Add corresponding elements
* const a = [1, 2, 3, 4]
* const b = [10, 20, 30, 40]
* const sums = Iterable.zipWith(a, b, (x, y) => x + y)
* console.log(Array.from(sums)) // [11, 22, 33, 44]
*
* // Combine strings
* const firstNames = ["John", "Jane", "Bob"]
* const lastNames = ["Doe", "Smith", "Johnson"]
* const fullNames = Iterable.zipWith(
* firstNames,
* lastNames,
* (first, last) => `${first} ${last}`
* )
* console.log(Array.from(fullNames)) // ["John Doe", "Jane Smith", "Bob Johnson"]
*
* // Different lengths - stops at shorter
* const short = [1, 2]
* const long = ["a", "b", "c", "d"]
* const combined = Iterable.zipWith(
* short,
* long,
* (num, letter) => `${num}${letter}`
* )
* console.log(Array.from(combined)) // ["1a", "2b"]
*
* // Complex transformations
* const prices = [10.99, 25.50, 5.00]
* const quantities = [2, 1, 3]
* const totals = Iterable.zipWith(prices, quantities, (price, qty) => {
* return Math.round(price * qty * 100) / 100 // round to 2 decimal places
* })
* console.log(Array.from(totals)) // [21.98, 25.5, 15]
* ```
*
* @category zipping
* @since 2.0.0
*/
(that: Iterable, f: (a: A, b: B) => C): (self: Iterable) => Iterable
/**
* Apply a function to pairs of elements at the same index in two `Iterable`s, collecting the results. If one
* input `Iterable` is short, excess elements of the longer `Iterable` are discarded.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Add corresponding elements
* const a = [1, 2, 3, 4]
* const b = [10, 20, 30, 40]
* const sums = Iterable.zipWith(a, b, (x, y) => x + y)
* console.log(Array.from(sums)) // [11, 22, 33, 44]
*
* // Combine strings
* const firstNames = ["John", "Jane", "Bob"]
* const lastNames = ["Doe", "Smith", "Johnson"]
* const fullNames = Iterable.zipWith(
* firstNames,
* lastNames,
* (first, last) => `${first} ${last}`
* )
* console.log(Array.from(fullNames)) // ["John Doe", "Jane Smith", "Bob Johnson"]
*
* // Different lengths - stops at shorter
* const short = [1, 2]
* const long = ["a", "b", "c", "d"]
* const combined = Iterable.zipWith(
* short,
* long,
* (num, letter) => `${num}${letter}`
* )
* console.log(Array.from(combined)) // ["1a", "2b"]
*
* // Complex transformations
* const prices = [10.99, 25.50, 5.00]
* const quantities = [2, 1, 3]
* const totals = Iterable.zipWith(prices, quantities, (price, qty) => {
* return Math.round(price * qty * 100) / 100 // round to 2 decimal places
* })
* console.log(Array.from(totals)) // [21.98, 25.5, 15]
* ```
*
* @category zipping
* @since 2.0.0
*/
(self: Iterable, that: Iterable, f: (a: A, b: B) => C): Iterable
} = dual(3, (self: Iterable, that: Iterable, f: (a: A, b: B) => C): Iterable => ({
[Symbol.iterator]() {
const selfIterator = self[Symbol.iterator]()
const thatIterator = that[Symbol.iterator]()
return {
next() {
const selfResult = selfIterator.next()
const thatResult = thatIterator.next()
if (selfResult.done || thatResult.done) {
return { done: true, value: undefined }
}
return { done: false, value: f(selfResult.value, thatResult.value) }
}
}
}
}))
/**
* Places an element in between members of an `Iterable`.
* If the input is a non-empty array, the result is also a non-empty array.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Join numbers with separator
* const numbers = [1, 2, 3, 4]
* const withCommas = Iterable.intersperse(numbers, ",")
* console.log(Array.from(withCommas)) // [1, ",", 2, ",", 3, ",", 4]
*
* // Join words with spaces
* const words = ["hello", "world", "from", "effect"]
* const sentence = Iterable.intersperse(words, " ")
* console.log(Array.from(sentence).join("")) // "hello world from effect"
*
* // Empty iterable remains empty
* const empty = Iterable.empty()
* const stillEmpty = Iterable.intersperse(empty, "-")
* console.log(Array.from(stillEmpty)) // []
*
* // Single element has no separators added
* const single = [42]
* const noSeparator = Iterable.intersperse(single, "|")
* console.log(Array.from(noSeparator)) // [42]
*
* // Build CSS-like strings
* const styles = ["color: red", "font-size: 14px", "margin: 10px"]
* const css = Iterable.intersperse(styles, "; ")
* console.log(Array.from(css).join("")) // "color: red; font-size: 14px; margin: 10px"
* ```
*
* @category concatenating
* @since 2.0.0
*/
export const intersperse: {
/**
* Places an element in between members of an `Iterable`.
* If the input is a non-empty array, the result is also a non-empty array.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Join numbers with separator
* const numbers = [1, 2, 3, 4]
* const withCommas = Iterable.intersperse(numbers, ",")
* console.log(Array.from(withCommas)) // [1, ",", 2, ",", 3, ",", 4]
*
* // Join words with spaces
* const words = ["hello", "world", "from", "effect"]
* const sentence = Iterable.intersperse(words, " ")
* console.log(Array.from(sentence).join("")) // "hello world from effect"
*
* // Empty iterable remains empty
* const empty = Iterable.empty()
* const stillEmpty = Iterable.intersperse(empty, "-")
* console.log(Array.from(stillEmpty)) // []
*
* // Single element has no separators added
* const single = [42]
* const noSeparator = Iterable.intersperse(single, "|")
* console.log(Array.from(noSeparator)) // [42]
*
* // Build CSS-like strings
* const styles = ["color: red", "font-size: 14px", "margin: 10px"]
* const css = Iterable.intersperse(styles, "; ")
* console.log(Array.from(css).join("")) // "color: red; font-size: 14px; margin: 10px"
* ```
*
* @category concatenating
* @since 2.0.0
*/
(middle: B): (self: Iterable) => Iterable
/**
* Places an element in between members of an `Iterable`.
* If the input is a non-empty array, the result is also a non-empty array.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Join numbers with separator
* const numbers = [1, 2, 3, 4]
* const withCommas = Iterable.intersperse(numbers, ",")
* console.log(Array.from(withCommas)) // [1, ",", 2, ",", 3, ",", 4]
*
* // Join words with spaces
* const words = ["hello", "world", "from", "effect"]
* const sentence = Iterable.intersperse(words, " ")
* console.log(Array.from(sentence).join("")) // "hello world from effect"
*
* // Empty iterable remains empty
* const empty = Iterable.empty()
* const stillEmpty = Iterable.intersperse(empty, "-")
* console.log(Array.from(stillEmpty)) // []
*
* // Single element has no separators added
* const single = [42]
* const noSeparator = Iterable.intersperse(single, "|")
* console.log(Array.from(noSeparator)) // [42]
*
* // Build CSS-like strings
* const styles = ["color: red", "font-size: 14px", "margin: 10px"]
* const css = Iterable.intersperse(styles, "; ")
* console.log(Array.from(css).join("")) // "color: red; font-size: 14px; margin: 10px"
* ```
*
* @category concatenating
* @since 2.0.0
*/
(self: Iterable, middle: B): Iterable
} = dual(2, (self: Iterable, middle: B): Iterable => ({
[Symbol.iterator]() {
const iterator = self[Symbol.iterator]()
let next = iterator.next()
let emitted = false
return {
next() {
if (next.done) {
return next
} else if (emitted) {
emitted = false
return { done: false, value: middle }
}
emitted = true
const result = next
next = iterator.next()
return result
}
}
}
}))
/**
* Returns a function that checks if an `Iterable` contains a given value using a provided `isEquivalent` function.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Custom equivalence for objects
* const byId = (a: { id: number }, b: { id: number }) => a.id === b.id
* const containsById = Iterable.containsWith(byId)
*
* const users = [{ id: 1 }, { id: 2 }]
* const hasUser1 = containsById(users, { id: 1 })
* console.log(hasUser1) // true (same id)
*
* // Case-insensitive string comparison
* const caseInsensitive = (a: string, b: string) =>
* a.toLowerCase() === b.toLowerCase()
* const containsCaseInsensitive = Iterable.containsWith(caseInsensitive)
*
* const words = ["Hello", "World"]
* const hasHello = containsCaseInsensitive(words, "hello")
* console.log(hasHello) // true
*
* // Approximate number comparison
* const approxEqual = (a: number, b: number) => Math.abs(a - b) < 0.1
* const containsApprox = Iterable.containsWith(approxEqual)
*
* const values = [1.0, 2.0, 3.0]
* const hasAlmostTwo = containsApprox(values, 2.05)
* console.log(hasAlmostTwo) // true
* ```
*
* @category elements
* @since 2.0.0
*/
export const containsWith = (isEquivalent: (self: A, that: A) => boolean): {
(a: A): (self: Iterable) => boolean
(self: Iterable, a: A): boolean
} =>
dual(2, (self: Iterable, a: A): boolean => {
for (const i of self) {
if (isEquivalent(a, i)) {
return true
}
}
return false
})
/**
* Returns a function that checks if a `Iterable` contains a given value using the default `Equivalence`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5]
* console.log(Iterable.contains(numbers, 3)) // true
* console.log(Iterable.contains(numbers, 6)) // false
*
* const letters = "hello"
* console.log(Iterable.contains(letters, "l")) // true
* console.log(Iterable.contains(letters, "x")) // false
*
* // Works with any iterable
* const range = Iterable.range(1, 100)
* console.log(Iterable.contains(range, 50)) // true
* console.log(Iterable.contains(range, 150)) // false
*
* // Curried version
* const containsThree = Iterable.contains(3)
* console.log(containsThree([1, 2, 3])) // true
* console.log(containsThree([4, 5, 6])) // false
* ```
*
* @category elements
* @since 2.0.0
*/
export const contains: {
/**
* Returns a function that checks if a `Iterable` contains a given value using the default `Equivalence`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5]
* console.log(Iterable.contains(numbers, 3)) // true
* console.log(Iterable.contains(numbers, 6)) // false
*
* const letters = "hello"
* console.log(Iterable.contains(letters, "l")) // true
* console.log(Iterable.contains(letters, "x")) // false
*
* // Works with any iterable
* const range = Iterable.range(1, 100)
* console.log(Iterable.contains(range, 50)) // true
* console.log(Iterable.contains(range, 150)) // false
*
* // Curried version
* const containsThree = Iterable.contains(3)
* console.log(containsThree([1, 2, 3])) // true
* console.log(containsThree([4, 5, 6])) // false
* ```
*
* @category elements
* @since 2.0.0
*/
(a: A): (self: Iterable) => boolean
/**
* Returns a function that checks if a `Iterable` contains a given value using the default `Equivalence`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5]
* console.log(Iterable.contains(numbers, 3)) // true
* console.log(Iterable.contains(numbers, 6)) // false
*
* const letters = "hello"
* console.log(Iterable.contains(letters, "l")) // true
* console.log(Iterable.contains(letters, "x")) // false
*
* // Works with any iterable
* const range = Iterable.range(1, 100)
* console.log(Iterable.contains(range, 50)) // true
* console.log(Iterable.contains(range, 150)) // false
*
* // Curried version
* const containsThree = Iterable.contains(3)
* console.log(containsThree([1, 2, 3])) // true
* console.log(containsThree([4, 5, 6])) // false
* ```
*
* @category elements
* @since 2.0.0
*/
(self: Iterable, a: A): boolean
} = containsWith(Equal.asEquivalence())
/**
* Splits an `Iterable` into length-`n` pieces. The last piece will be shorter if `n` does not evenly divide the length of
* the `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
* const chunks = Iterable.chunksOf(numbers, 3)
* console.log(Array.from(chunks).map((chunk) => Array.from(chunk)))
* // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
*
* // Last chunk can be shorter
* const uneven = [1, 2, 3, 4, 5, 6, 7]
* const chunks2 = Iterable.chunksOf(uneven, 3)
* console.log(Array.from(chunks2).map((chunk) => Array.from(chunk)))
* // [[1, 2, 3], [4, 5, 6], [7]]
*
* // Chunk size larger than iterable
* const small = [1, 2]
* const chunks3 = Iterable.chunksOf(small, 5)
* console.log(Array.from(chunks3).map((chunk) => Array.from(chunk)))
* // [[1, 2]]
*
* // Process data in batches
* const data = Iterable.range(1, 100)
* const batches = Iterable.chunksOf(data, 10)
* const batchSums = Iterable.map(
* batches,
* (batch) => Iterable.reduce(batch, 0, (sum, n) => sum + n)
* )
* console.log(Array.from(Iterable.take(batchSums, 3))) // [55, 155, 255]
* ```
*
* @category splitting
* @since 2.0.0
*/
export const chunksOf: {
/**
* Splits an `Iterable` into length-`n` pieces. The last piece will be shorter if `n` does not evenly divide the length of
* the `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
* const chunks = Iterable.chunksOf(numbers, 3)
* console.log(Array.from(chunks).map((chunk) => Array.from(chunk)))
* // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
*
* // Last chunk can be shorter
* const uneven = [1, 2, 3, 4, 5, 6, 7]
* const chunks2 = Iterable.chunksOf(uneven, 3)
* console.log(Array.from(chunks2).map((chunk) => Array.from(chunk)))
* // [[1, 2, 3], [4, 5, 6], [7]]
*
* // Chunk size larger than iterable
* const small = [1, 2]
* const chunks3 = Iterable.chunksOf(small, 5)
* console.log(Array.from(chunks3).map((chunk) => Array.from(chunk)))
* // [[1, 2]]
*
* // Process data in batches
* const data = Iterable.range(1, 100)
* const batches = Iterable.chunksOf(data, 10)
* const batchSums = Iterable.map(
* batches,
* (batch) => Iterable.reduce(batch, 0, (sum, n) => sum + n)
* )
* console.log(Array.from(Iterable.take(batchSums, 3))) // [55, 155, 255]
* ```
*
* @category splitting
* @since 2.0.0
*/
(n: number): (self: Iterable) => Iterable>
/**
* Splits an `Iterable` into length-`n` pieces. The last piece will be shorter if `n` does not evenly divide the length of
* the `Iterable`.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
* const chunks = Iterable.chunksOf(numbers, 3)
* console.log(Array.from(chunks).map((chunk) => Array.from(chunk)))
* // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
*
* // Last chunk can be shorter
* const uneven = [1, 2, 3, 4, 5, 6, 7]
* const chunks2 = Iterable.chunksOf(uneven, 3)
* console.log(Array.from(chunks2).map((chunk) => Array.from(chunk)))
* // [[1, 2, 3], [4, 5, 6], [7]]
*
* // Chunk size larger than iterable
* const small = [1, 2]
* const chunks3 = Iterable.chunksOf(small, 5)
* console.log(Array.from(chunks3).map((chunk) => Array.from(chunk)))
* // [[1, 2]]
*
* // Process data in batches
* const data = Iterable.range(1, 100)
* const batches = Iterable.chunksOf(data, 10)
* const batchSums = Iterable.map(
* batches,
* (batch) => Iterable.reduce(batch, 0, (sum, n) => sum + n)
* )
* console.log(Array.from(Iterable.take(batchSums, 3))) // [55, 155, 255]
* ```
*
* @category splitting
* @since 2.0.0
*/
(self: Iterable, n: number): Iterable>
} = dual(2, (self: Iterable, n: number): Iterable> => {
const safeN = Math.max(1, Math.floor(n))
return ({
[Symbol.iterator]() {
let iterator: Iterator | undefined = self[Symbol.iterator]()
return {
next() {
if (iterator === undefined) {
return { done: true, value: undefined }
}
const chunk: Array = []
for (let i = 0; i < safeN; i++) {
const result = iterator.next()
if (result.done) {
iterator = undefined
return chunk.length === 0 ? { done: true, value: undefined } : { done: false, value: chunk }
}
chunk.push(result.value)
}
return { done: false, value: chunk }
}
}
}
})
})
/**
* Group equal, consecutive elements of an `Iterable` into `NonEmptyArray`s using the provided `isEquivalent` function.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Group consecutive equal numbers
* const numbers = [1, 1, 2, 2, 2, 3, 1, 1]
* const grouped = Iterable.groupWith(numbers, (a, b) => a === b)
* console.log(Array.from(grouped))
* // [[1, 1], [2, 2, 2], [3], [1, 1]]
*
* // Case-insensitive grouping of strings
* const words = ["Apple", "APPLE", "banana", "Banana", "cherry"]
* const caseInsensitive = (a: string, b: string) =>
* a.toLowerCase() === b.toLowerCase()
* const groupedWords = Iterable.groupWith(words, caseInsensitive)
* console.log(Array.from(groupedWords))
* // [["Apple", "APPLE"], ["banana", "Banana"], ["cherry"]]
*
* // Group by approximate equality
* const floats = [1.1, 1.12, 1.9, 2.01, 2.05, 3.5]
* const approxEqual = (a: number, b: number) => Math.abs(a - b) < 0.2
* const groupedFloats = Iterable.groupWith(floats, approxEqual)
* console.log(Array.from(groupedFloats))
* // [[1.1, 1.12], [1.9, 2.01, 2.05], [3.5]]
*
* // Only groups consecutive elements
* const scattered = [1, 2, 1, 2, 1]
* const scatteredGroups = Iterable.groupWith(scattered, (a, b) => a === b)
* console.log(Array.from(scatteredGroups))
* // [[1], [2], [1], [2], [1]] (no grouping since none are consecutive)
* ```
*
* @category grouping
* @since 2.0.0
*/
export const groupWith: {
/**
* Group equal, consecutive elements of an `Iterable` into `NonEmptyArray`s using the provided `isEquivalent` function.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Group consecutive equal numbers
* const numbers = [1, 1, 2, 2, 2, 3, 1, 1]
* const grouped = Iterable.groupWith(numbers, (a, b) => a === b)
* console.log(Array.from(grouped))
* // [[1, 1], [2, 2, 2], [3], [1, 1]]
*
* // Case-insensitive grouping of strings
* const words = ["Apple", "APPLE", "banana", "Banana", "cherry"]
* const caseInsensitive = (a: string, b: string) =>
* a.toLowerCase() === b.toLowerCase()
* const groupedWords = Iterable.groupWith(words, caseInsensitive)
* console.log(Array.from(groupedWords))
* // [["Apple", "APPLE"], ["banana", "Banana"], ["cherry"]]
*
* // Group by approximate equality
* const floats = [1.1, 1.12, 1.9, 2.01, 2.05, 3.5]
* const approxEqual = (a: number, b: number) => Math.abs(a - b) < 0.2
* const groupedFloats = Iterable.groupWith(floats, approxEqual)
* console.log(Array.from(groupedFloats))
* // [[1.1, 1.12], [1.9, 2.01, 2.05], [3.5]]
*
* // Only groups consecutive elements
* const scattered = [1, 2, 1, 2, 1]
* const scatteredGroups = Iterable.groupWith(scattered, (a, b) => a === b)
* console.log(Array.from(scatteredGroups))
* // [[1], [2], [1], [2], [1]] (no grouping since none are consecutive)
* ```
*
* @category grouping
* @since 2.0.0
*/
(isEquivalent: (self: A, that: A) => boolean): (self: Iterable) => Iterable>
/**
* Group equal, consecutive elements of an `Iterable` into `NonEmptyArray`s using the provided `isEquivalent` function.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Group consecutive equal numbers
* const numbers = [1, 1, 2, 2, 2, 3, 1, 1]
* const grouped = Iterable.groupWith(numbers, (a, b) => a === b)
* console.log(Array.from(grouped))
* // [[1, 1], [2, 2, 2], [3], [1, 1]]
*
* // Case-insensitive grouping of strings
* const words = ["Apple", "APPLE", "banana", "Banana", "cherry"]
* const caseInsensitive = (a: string, b: string) =>
* a.toLowerCase() === b.toLowerCase()
* const groupedWords = Iterable.groupWith(words, caseInsensitive)
* console.log(Array.from(groupedWords))
* // [["Apple", "APPLE"], ["banana", "Banana"], ["cherry"]]
*
* // Group by approximate equality
* const floats = [1.1, 1.12, 1.9, 2.01, 2.05, 3.5]
* const approxEqual = (a: number, b: number) => Math.abs(a - b) < 0.2
* const groupedFloats = Iterable.groupWith(floats, approxEqual)
* console.log(Array.from(groupedFloats))
* // [[1.1, 1.12], [1.9, 2.01, 2.05], [3.5]]
*
* // Only groups consecutive elements
* const scattered = [1, 2, 1, 2, 1]
* const scatteredGroups = Iterable.groupWith(scattered, (a, b) => a === b)
* console.log(Array.from(scatteredGroups))
* // [[1], [2], [1], [2], [1]] (no grouping since none are consecutive)
* ```
*
* @category grouping
* @since 2.0.0
*/
(self: Iterable, isEquivalent: (self: A, that: A) => boolean): Iterable>
} = dual(
2,
(self: Iterable, isEquivalent: (self: A, that: A) => boolean): Iterable> => ({
[Symbol.iterator]() {
const iterator = self[Symbol.iterator]()
let nextResult: IteratorResult | undefined
return {
next() {
let result: IteratorResult
if (nextResult !== undefined) {
if (nextResult.done) {
return { done: true, value: undefined }
}
result = nextResult
nextResult = undefined
} else {
result = iterator.next()
if (result.done) {
return { done: true, value: undefined }
}
}
const chunk: NonEmptyArray = [result.value]
while (true) {
const next = iterator.next()
if (next.done || !isEquivalent(result.value, next.value)) {
nextResult = next
return { done: false, value: chunk }
}
chunk.push(next.value)
}
}
}
}
})
)
/**
* Group equal, consecutive elements of an `Iterable` into `NonEmptyArray`s.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const numbers = [1, 1, 2, 2, 2, 3, 1, 1]
* const grouped = Iterable.group(numbers)
* console.log(Array.from(grouped))
* // [[1, 1], [2, 2, 2], [3], [1, 1]]
*
* const letters = "aabbccaa"
* const groupedLetters = Iterable.group(letters)
* console.log(Array.from(groupedLetters))
* // [["a", "a"], ["b", "b"], ["c", "c"], ["a", "a"]]
*
* // Works with objects using deep equality
* const objects = [
* { type: "A", value: 1 },
* { type: "A", value: 1 },
* { type: "B", value: 2 },
* { type: "A", value: 1 }
* ]
* const groupedObjects = Iterable.group(objects)
* console.log(Array.from(groupedObjects).length) // 3 groups
* // Note: Only consecutive equal objects are grouped together
* ```
*
* @category grouping
* @since 2.0.0
*/
export const group: (self: Iterable) => Iterable> = groupWith(
Equal.asEquivalence()
)
/**
* Splits an `Iterable` into sub-non-empty-arrays stored in an object, based on the result of calling a `string`-returning
* function on each element, and grouping the results according to values returned
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Group by string length
* const words = ["a", "bb", "ccc", "dd", "eee", "f"]
* const byLength = Iterable.groupBy(words, (word) => word.length.toString())
* console.log(byLength)
* // { "1": ["a", "f"], "2": ["bb", "dd"], "3": ["ccc", "eee"] }
*
* // Group by first letter
* const names = ["Alice", "Bob", "Charlie", "David", "Anna", "Betty"]
* const byFirstLetter = Iterable.groupBy(names, (name) => name[0])
* console.log(byFirstLetter)
* // { "A": ["Alice", "Anna"], "B": ["Bob", "Betty"], "C": ["Charlie"], "D": ["David"] }
*
* // Group by category
* const items = [
* { name: "apple", category: "fruit" },
* { name: "carrot", category: "vegetable" },
* { name: "banana", category: "fruit" },
* { name: "broccoli", category: "vegetable" }
* ]
* const byCategory = Iterable.groupBy(items, (item) => item.category)
* console.log(byCategory)
* // {
* // "fruit": [{ name: "apple", category: "fruit" }, { name: "banana", category: "fruit" }],
* // "vegetable": [{ name: "carrot", category: "vegetable" }, { name: "broccoli", category: "vegetable" }]
* // }
*
* // Group numbers by even/odd
* const numbers = [1, 2, 3, 4, 5, 6]
* const evenOdd = Iterable.groupBy(numbers, (n) => n % 2 === 0 ? "even" : "odd")
* console.log(evenOdd)
* // { "odd": [1, 3, 5], "even": [2, 4, 6] }
* ```
*
* @category grouping
* @since 2.0.0
*/
export const groupBy: {
/**
* Splits an `Iterable` into sub-non-empty-arrays stored in an object, based on the result of calling a `string`-returning
* function on each element, and grouping the results according to values returned
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Group by string length
* const words = ["a", "bb", "ccc", "dd", "eee", "f"]
* const byLength = Iterable.groupBy(words, (word) => word.length.toString())
* console.log(byLength)
* // { "1": ["a", "f"], "2": ["bb", "dd"], "3": ["ccc", "eee"] }
*
* // Group by first letter
* const names = ["Alice", "Bob", "Charlie", "David", "Anna", "Betty"]
* const byFirstLetter = Iterable.groupBy(names, (name) => name[0])
* console.log(byFirstLetter)
* // { "A": ["Alice", "Anna"], "B": ["Bob", "Betty"], "C": ["Charlie"], "D": ["David"] }
*
* // Group by category
* const items = [
* { name: "apple", category: "fruit" },
* { name: "carrot", category: "vegetable" },
* { name: "banana", category: "fruit" },
* { name: "broccoli", category: "vegetable" }
* ]
* const byCategory = Iterable.groupBy(items, (item) => item.category)
* console.log(byCategory)
* // {
* // "fruit": [{ name: "apple", category: "fruit" }, { name: "banana", category: "fruit" }],
* // "vegetable": [{ name: "carrot", category: "vegetable" }, { name: "broccoli", category: "vegetable" }]
* // }
*
* // Group numbers by even/odd
* const numbers = [1, 2, 3, 4, 5, 6]
* const evenOdd = Iterable.groupBy(numbers, (n) => n % 2 === 0 ? "even" : "odd")
* console.log(evenOdd)
* // { "odd": [1, 3, 5], "even": [2, 4, 6] }
* ```
*
* @category grouping
* @since 2.0.0
*/
(f: (a: A) => K): (self: Iterable) => Record, NonEmptyArray>
/**
* Splits an `Iterable` into sub-non-empty-arrays stored in an object, based on the result of calling a `string`-returning
* function on each element, and grouping the results according to values returned
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Group by string length
* const words = ["a", "bb", "ccc", "dd", "eee", "f"]
* const byLength = Iterable.groupBy(words, (word) => word.length.toString())
* console.log(byLength)
* // { "1": ["a", "f"], "2": ["bb", "dd"], "3": ["ccc", "eee"] }
*
* // Group by first letter
* const names = ["Alice", "Bob", "Charlie", "David", "Anna", "Betty"]
* const byFirstLetter = Iterable.groupBy(names, (name) => name[0])
* console.log(byFirstLetter)
* // { "A": ["Alice", "Anna"], "B": ["Bob", "Betty"], "C": ["Charlie"], "D": ["David"] }
*
* // Group by category
* const items = [
* { name: "apple", category: "fruit" },
* { name: "carrot", category: "vegetable" },
* { name: "banana", category: "fruit" },
* { name: "broccoli", category: "vegetable" }
* ]
* const byCategory = Iterable.groupBy(items, (item) => item.category)
* console.log(byCategory)
* // {
* // "fruit": [{ name: "apple", category: "fruit" }, { name: "banana", category: "fruit" }],
* // "vegetable": [{ name: "carrot", category: "vegetable" }, { name: "broccoli", category: "vegetable" }]
* // }
*
* // Group numbers by even/odd
* const numbers = [1, 2, 3, 4, 5, 6]
* const evenOdd = Iterable.groupBy(numbers, (n) => n % 2 === 0 ? "even" : "odd")
* console.log(evenOdd)
* // { "odd": [1, 3, 5], "even": [2, 4, 6] }
* ```
*
* @category grouping
* @since 2.0.0
*/
(self: Iterable, f: (a: A) => K): Record, NonEmptyArray>
} = dual(2, (
self: Iterable,
f: (a: A) => K
): Record, NonEmptyArray> => {
const out: Record> = {}
for (const a of self) {
const k = f(a)
if (Object.hasOwn(out, k)) {
out[k].push(a)
} else {
out[k] = [a]
}
}
return out
})
const constEmpty: Iterable = {
[Symbol.iterator]() {
return constEmptyIterator
}
}
const constEmptyIterator: Iterator = {
next() {
return { done: true, value: undefined }
}
}
/**
* Creates an empty iterable that yields no elements.
*
* This function returns a reusable empty iterable that can be used as a base case
* for operations or when you need to represent "no data" in a type-safe way.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const empty = Iterable.empty()
* console.log(Array.from(empty)) // []
* console.log(Iterable.isEmpty(empty)) // true
*
* // Useful as base case for reductions
* const hasData = true
* const result = hasData
* ? Iterable.range(1, 5)
* : Iterable.empty()
* ```
*
* @category constructors
* @since 2.0.0
*/
export const empty = (): Iterable => constEmpty
/**
* Creates an iterable containing a single element.
*
* This is useful for wrapping a single value in an iterable context,
* allowing it to be used with other iterable operations.
*
* @param a - The single element to wrap in an iterable
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* const single = Iterable.of(42)
* console.log(Array.from(single)) // [42]
*
* // Useful for creating homogeneous sequences
* const sequences = [
* Iterable.of("hello"),
* Iterable.range(1, 3),
* Iterable.empty()
* ]
*
* // Can be used with flatMap for conditional inclusion
* const numbers = [1, 2, 3, 4, 5]
* const evensOnly = Iterable.flatMap(
* numbers,
* (n) => n % 2 === 0 ? Iterable.of(n) : Iterable.empty()
* )
* console.log(Array.from(evensOnly)) // [2, 4]
* ```
*
* @category constructors
* @since 2.0.0
*/
export const of = (a: A): Iterable => [a]
/**
* Transforms each element of an iterable using a function.
*
* This is one of the most fundamental operations for working with iterables.
* It applies a transformation function to each element, creating a new iterable
* with the transformed values. The operation is lazy - elements are only
* transformed when the iterable is consumed.
*
* @param self - The source iterable to transform
* @param f - Function that transforms each element (receives value and index)
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Transform numbers to their squares
* const numbers = [1, 2, 3, 4, 5]
* const squares = Iterable.map(numbers, (x) => x * x)
* console.log(Array.from(squares)) // [1, 4, 9, 16, 25]
*
* // Use index in transformation
* const indexed = Iterable.map(["a", "b", "c"], (char, i) => `${i}: ${char}`)
* console.log(Array.from(indexed)) // ["0: a", "1: b", "2: c"]
*
* // Chain transformations
* const result = Iterable.map(
* Iterable.map([1, 2, 3], (x) => x * 2),
* (x) => x + 1
* )
* console.log(Array.from(result)) // [3, 5, 7]
* ```
*
* @category mapping
* @since 2.0.0
*/
export const map: {
/**
* Transforms each element of an iterable using a function.
*
* This is one of the most fundamental operations for working with iterables.
* It applies a transformation function to each element, creating a new iterable
* with the transformed values. The operation is lazy - elements are only
* transformed when the iterable is consumed.
*
* @param self - The source iterable to transform
* @param f - Function that transforms each element (receives value and index)
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Transform numbers to their squares
* const numbers = [1, 2, 3, 4, 5]
* const squares = Iterable.map(numbers, (x) => x * x)
* console.log(Array.from(squares)) // [1, 4, 9, 16, 25]
*
* // Use index in transformation
* const indexed = Iterable.map(["a", "b", "c"], (char, i) => `${i}: ${char}`)
* console.log(Array.from(indexed)) // ["0: a", "1: b", "2: c"]
*
* // Chain transformations
* const result = Iterable.map(
* Iterable.map([1, 2, 3], (x) => x * 2),
* (x) => x + 1
* )
* console.log(Array.from(result)) // [3, 5, 7]
* ```
*
* @category mapping
* @since 2.0.0
*/
(f: (a: NoInfer, i: number) => B): (self: Iterable) => Iterable
/**
* Transforms each element of an iterable using a function.
*
* This is one of the most fundamental operations for working with iterables.
* It applies a transformation function to each element, creating a new iterable
* with the transformed values. The operation is lazy - elements are only
* transformed when the iterable is consumed.
*
* @param self - The source iterable to transform
* @param f - Function that transforms each element (receives value and index)
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Transform numbers to their squares
* const numbers = [1, 2, 3, 4, 5]
* const squares = Iterable.map(numbers, (x) => x * x)
* console.log(Array.from(squares)) // [1, 4, 9, 16, 25]
*
* // Use index in transformation
* const indexed = Iterable.map(["a", "b", "c"], (char, i) => `${i}: ${char}`)
* console.log(Array.from(indexed)) // ["0: a", "1: b", "2: c"]
*
* // Chain transformations
* const result = Iterable.map(
* Iterable.map([1, 2, 3], (x) => x * 2),
* (x) => x + 1
* )
* console.log(Array.from(result)) // [3, 5, 7]
* ```
*
* @category mapping
* @since 2.0.0
*/
(self: Iterable, f: (a: NoInfer, i: number) => B): Iterable
} = dual(2, (self: Iterable, f: (a: A, i: number) => B): Iterable => ({
[Symbol.iterator]() {
const iterator = self[Symbol.iterator]()
let i = 0
return {
next() {
const result = iterator.next()
if (result.done) {
return { done: true, value: undefined }
}
return { done: false, value: f(result.value, i++) }
}
}
}
}))
/**
* Applies a function to each element in an Iterable and returns a new Iterable containing the concatenated mapped elements.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Expand each number to a range
* const numbers = [1, 2, 3]
* const expanded = Iterable.flatMap(numbers, (n) => Iterable.range(1, n))
* console.log(Array.from(expanded)) // [1, 1, 2, 1, 2, 3]
*
* // Split strings into characters
* const words = ["hi", "bye"]
* const chars = Iterable.flatMap(words, (word) => word)
* console.log(Array.from(chars)) // ["h", "i", "b", "y", "e"]
*
* // Conditional expansion with empty iterables
* const values = [1, 2, 3, 4, 5]
* const evenMultiples = Iterable.flatMap(
* values,
* (n) => n % 2 === 0 ? [n, n * 2, n * 3] : []
* )
* console.log(Array.from(evenMultiples)) // [2, 4, 6, 4, 8, 12]
*
* // Use index in transformation
* const letters = ["a", "b", "c"]
* const indexed = Iterable.flatMap(
* letters,
* (letter, i) => Iterable.replicate(letter, i + 1)
* )
* console.log(Array.from(indexed)) // ["a", "b", "b", "c", "c", "c"]
* ```
*
* @category sequencing
* @since 2.0.0
*/
export const flatMap: {
/**
* Applies a function to each element in an Iterable and returns a new Iterable containing the concatenated mapped elements.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Expand each number to a range
* const numbers = [1, 2, 3]
* const expanded = Iterable.flatMap(numbers, (n) => Iterable.range(1, n))
* console.log(Array.from(expanded)) // [1, 1, 2, 1, 2, 3]
*
* // Split strings into characters
* const words = ["hi", "bye"]
* const chars = Iterable.flatMap(words, (word) => word)
* console.log(Array.from(chars)) // ["h", "i", "b", "y", "e"]
*
* // Conditional expansion with empty iterables
* const values = [1, 2, 3, 4, 5]
* const evenMultiples = Iterable.flatMap(
* values,
* (n) => n % 2 === 0 ? [n, n * 2, n * 3] : []
* )
* console.log(Array.from(evenMultiples)) // [2, 4, 6, 4, 8, 12]
*
* // Use index in transformation
* const letters = ["a", "b", "c"]
* const indexed = Iterable.flatMap(
* letters,
* (letter, i) => Iterable.replicate(letter, i + 1)
* )
* console.log(Array.from(indexed)) // ["a", "b", "b", "c", "c", "c"]
* ```
*
* @category sequencing
* @since 2.0.0
*/
(f: (a: NoInfer, i: number) => Iterable): (self: Iterable) => Iterable
/**
* Applies a function to each element in an Iterable and returns a new Iterable containing the concatenated mapped elements.
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Expand each number to a range
* const numbers = [1, 2, 3]
* const expanded = Iterable.flatMap(numbers, (n) => Iterable.range(1, n))
* console.log(Array.from(expanded)) // [1, 1, 2, 1, 2, 3]
*
* // Split strings into characters
* const words = ["hi", "bye"]
* const chars = Iterable.flatMap(words, (word) => word)
* console.log(Array.from(chars)) // ["h", "i", "b", "y", "e"]
*
* // Conditional expansion with empty iterables
* const values = [1, 2, 3, 4, 5]
* const evenMultiples = Iterable.flatMap(
* values,
* (n) => n % 2 === 0 ? [n, n * 2, n * 3] : []
* )
* console.log(Array.from(evenMultiples)) // [2, 4, 6, 4, 8, 12]
*
* // Use index in transformation
* const letters = ["a", "b", "c"]
* const indexed = Iterable.flatMap(
* letters,
* (letter, i) => Iterable.replicate(letter, i + 1)
* )
* console.log(Array.from(indexed)) // ["a", "b", "b", "c", "c", "c"]
* ```
*
* @category sequencing
* @since 2.0.0
*/
(self: Iterable, f: (a: NoInfer, i: number) => Iterable): Iterable
} = dual(
2,
(self: Iterable, f: (a: A, i: number) => Iterable): Iterable => flatten(map(self, f))
)
/**
* Flattens an Iterable of Iterables into a single Iterable
*
* @example
* ```ts
* import { Iterable } from "effect"
*
* // Flatten nested arrays
* const nested = [[1, 2], [3, 4], [5, 6]]
* const flat = Iterable.flatten(nested)
* console.log(Array.from(flat)) // [1, 2, 3, 4, 5, 6]
*
* // Flatten different iterable types
* const mixed: Array> = ["ab", "cd"]
* const flatMixed = Iterable.flatten(mixed)
* console.log(Array.from(flatMixed)) // ["a", "b", "c", "d"]
*
* // Flatten deeply nested (only one level)
* const deepNested = [[[1, 2]], [[3, 4]]]
* const oneLevelFlat = Iterable.flatten(deepNested)
* console.log(Array.from(oneLevelFlat).map((arr) => Array.from(arr)))
* // [[1, 2], [3, 4]] (still contains arrays)
*
* // Empty iterables are handled correctly
* const withEmpty = [[1, 2], [], [3, 4], []]
* const flatWithEmpty = Iterable.flatten(withEmpty)
* console.log(Array.from(flatWithEmpty)) // [1, 2, 3, 4]
* ```
*
* @category sequencing
* @since 2.0.0
*/
export const flatten = (self: Iterable>): Iterable => ({
[Symbol.iterator]() {
const outerIterator = self[Symbol.iterator]()
let innerIterator: Iterator | undefined
function next() {
if (innerIterator === undefined) {
const next = outerIterator.next()
if (next.done) {
return next
}
innerIterator = next.value[Symbol.iterator]()
}
const result = innerIterator.next()
if (result.done) {
innerIterator = undefined
return next()
}
return result
}
return { next }
}
})
/**
* Transforms elements of an iterable using a function that returns a `Result`, keeping only successful values.
*
* This combines mapping and filtering in a single operation - the function is applied to each element,
* and only elements that result in `Result.succeed` are included in the result.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Result from "effect/Result"
*
* // Parse strings to numbers, keeping only valid ones
* const strings = ["1", "2", "invalid", "4", "not-a-number"]
* const numbers = Iterable.filterMap(strings, (s) => {
* const num = parseInt(s)
* return isNaN(num) ? Result.failVoid : Result.succeed(num)
* })
* console.log(Array.from(numbers)) // [1, 2, 4]
*
* // Extract specific properties from objects
* const users = [
* { name: "Alice", age: 25, email: "alice@example.com" },
* { name: "Bob", age: 17, email: undefined },
* { name: "Charlie", age: 30, email: "charlie@example.com" },
* { name: "David", age: 16, email: undefined }
* ]
* const adultEmails = Iterable.filterMap(
* users,
* (user) =>
* user.age >= 18 && user.email ? Result.succeed(user.email) : Result.failVoid
* )
* console.log(Array.from(adultEmails)) // ["alice@example.com", "charlie@example.com"]
*
* // Use index in transformation
* const items = ["a", "b", "c", "d", "e"]
* const evenIndexItems = Iterable.filterMap(
* items,
* (item, i) => i % 2 === 0 ? Result.succeed(`${i}: ${item}`) : Result.failVoid
* )
* console.log(Array.from(evenIndexItems)) // ["0: a", "2: c", "4: e"]
* ```
*
* @category filtering
* @since 2.0.0
*/
export const filterMap: {
/**
* Transforms elements of an iterable using a function that returns a `Result`, keeping only successful values.
*
* This combines mapping and filtering in a single operation - the function is applied to each element,
* and only elements that result in `Result.succeed` are included in the result.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Result from "effect/Result"
*
* // Parse strings to numbers, keeping only valid ones
* const strings = ["1", "2", "invalid", "4", "not-a-number"]
* const numbers = Iterable.filterMap(strings, (s) => {
* const num = parseInt(s)
* return isNaN(num) ? Result.failVoid : Result.succeed(num)
* })
* console.log(Array.from(numbers)) // [1, 2, 4]
*
* // Extract specific properties from objects
* const users = [
* { name: "Alice", age: 25, email: "alice@example.com" },
* { name: "Bob", age: 17, email: undefined },
* { name: "Charlie", age: 30, email: "charlie@example.com" },
* { name: "David", age: 16, email: undefined }
* ]
* const adultEmails = Iterable.filterMap(
* users,
* (user) =>
* user.age >= 18 && user.email ? Result.succeed(user.email) : Result.failVoid
* )
* console.log(Array.from(adultEmails)) // ["alice@example.com", "charlie@example.com"]
*
* // Use index in transformation
* const items = ["a", "b", "c", "d", "e"]
* const evenIndexItems = Iterable.filterMap(
* items,
* (item, i) => i % 2 === 0 ? Result.succeed(`${i}: ${item}`) : Result.failVoid
* )
* console.log(Array.from(evenIndexItems)) // ["0: a", "2: c", "4: e"]
* ```
*
* @category filtering
* @since 2.0.0
*/
(f: (input: A, i: number) => Result): (self: Iterable) => Iterable
/**
* Transforms elements of an iterable using a function that returns a `Result`, keeping only successful values.
*
* This combines mapping and filtering in a single operation - the function is applied to each element,
* and only elements that result in `Result.succeed` are included in the result.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Result from "effect/Result"
*
* // Parse strings to numbers, keeping only valid ones
* const strings = ["1", "2", "invalid", "4", "not-a-number"]
* const numbers = Iterable.filterMap(strings, (s) => {
* const num = parseInt(s)
* return isNaN(num) ? Result.failVoid : Result.succeed(num)
* })
* console.log(Array.from(numbers)) // [1, 2, 4]
*
* // Extract specific properties from objects
* const users = [
* { name: "Alice", age: 25, email: "alice@example.com" },
* { name: "Bob", age: 17, email: undefined },
* { name: "Charlie", age: 30, email: "charlie@example.com" },
* { name: "David", age: 16, email: undefined }
* ]
* const adultEmails = Iterable.filterMap(
* users,
* (user) =>
* user.age >= 18 && user.email ? Result.succeed(user.email) : Result.failVoid
* )
* console.log(Array.from(adultEmails)) // ["alice@example.com", "charlie@example.com"]
*
* // Use index in transformation
* const items = ["a", "b", "c", "d", "e"]
* const evenIndexItems = Iterable.filterMap(
* items,
* (item, i) => i % 2 === 0 ? Result.succeed(`${i}: ${item}`) : Result.failVoid
* )
* console.log(Array.from(evenIndexItems)) // ["0: a", "2: c", "4: e"]
* ```
*
* @category filtering
* @since 2.0.0
*/
(self: Iterable, f: (input: A, i: number) => Result): Iterable
} = dual(
2,
(self: Iterable, f: (input: A, i: number) => Result): Iterable => ({
[Symbol.iterator]() {
const iterator = self[Symbol.iterator]()
let i = 0
return {
next() {
let result = iterator.next()
while (!result.done) {
const next = f(result.value, i++)
if (R.isSuccess(next)) {
return { done: false, value: next.success }
}
result = iterator.next()
}
return { done: true, value: undefined }
}
}
}
})
)
/**
* Transforms all elements of the `Iterable` for as long as the specified function succeeds.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Result from "effect/Result"
*
* // Parse numbers until we hit an invalid one
* const strings = ["1", "2", "3", "invalid", "4", "5"]
* const numbers = Iterable.filterMapWhile(strings, (s) => {
* const num = parseInt(s)
* return isNaN(num) ? Result.failVoid : Result.succeed(num)
* })
* console.log(Array.from(numbers)) // [1, 2, 3] (stops at "invalid")
*
* // Take elements while they meet a condition and transform them
* const values = [2, 4, 6, 7, 8, 10]
* const doubledEvens = Iterable.filterMapWhile(
* values,
* (n) => n % 2 === 0 ? Result.succeed(n * 2) : Result.failVoid
* )
* console.log(Array.from(doubledEvens)) // [4, 8, 12] (stops at 7)
*
* // Process with index until condition fails
* const letters = ["a", "b", "c", "d", "e"]
* const indexedUntilC = Iterable.filterMapWhile(
* letters,
* (letter, i) => letter !== "c" ? Result.succeed(`${i}: ${letter}`) : Result.failVoid
* )
* console.log(Array.from(indexedUntilC)) // ["0: a", "1: b"] (stops at "c")
* ```
*
* @category filtering
* @since 2.0.0
*/
export const filterMapWhile: {
/**
* Transforms all elements of the `Iterable` for as long as the specified function succeeds.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Result from "effect/Result"
*
* // Parse numbers until we hit an invalid one
* const strings = ["1", "2", "3", "invalid", "4", "5"]
* const numbers = Iterable.filterMapWhile(strings, (s) => {
* const num = parseInt(s)
* return isNaN(num) ? Result.failVoid : Result.succeed(num)
* })
* console.log(Array.from(numbers)) // [1, 2, 3] (stops at "invalid")
*
* // Take elements while they meet a condition and transform them
* const values = [2, 4, 6, 7, 8, 10]
* const doubledEvens = Iterable.filterMapWhile(
* values,
* (n) => n % 2 === 0 ? Result.succeed(n * 2) : Result.failVoid
* )
* console.log(Array.from(doubledEvens)) // [4, 8, 12] (stops at 7)
*
* // Process with index until condition fails
* const letters = ["a", "b", "c", "d", "e"]
* const indexedUntilC = Iterable.filterMapWhile(
* letters,
* (letter, i) => letter !== "c" ? Result.succeed(`${i}: ${letter}`) : Result.failVoid
* )
* console.log(Array.from(indexedUntilC)) // ["0: a", "1: b"] (stops at "c")
* ```
*
* @category filtering
* @since 2.0.0
*/
(f: (input: A, i: number) => Result): (self: Iterable) => Iterable
/**
* Transforms all elements of the `Iterable` for as long as the specified function succeeds.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Result from "effect/Result"
*
* // Parse numbers until we hit an invalid one
* const strings = ["1", "2", "3", "invalid", "4", "5"]
* const numbers = Iterable.filterMapWhile(strings, (s) => {
* const num = parseInt(s)
* return isNaN(num) ? Result.failVoid : Result.succeed(num)
* })
* console.log(Array.from(numbers)) // [1, 2, 3] (stops at "invalid")
*
* // Take elements while they meet a condition and transform them
* const values = [2, 4, 6, 7, 8, 10]
* const doubledEvens = Iterable.filterMapWhile(
* values,
* (n) => n % 2 === 0 ? Result.succeed(n * 2) : Result.failVoid
* )
* console.log(Array.from(doubledEvens)) // [4, 8, 12] (stops at 7)
*
* // Process with index until condition fails
* const letters = ["a", "b", "c", "d", "e"]
* const indexedUntilC = Iterable.filterMapWhile(
* letters,
* (letter, i) => letter !== "c" ? Result.succeed(`${i}: ${letter}`) : Result.failVoid
* )
* console.log(Array.from(indexedUntilC)) // ["0: a", "1: b"] (stops at "c")
* ```
*
* @category filtering
* @since 2.0.0
*/
(self: Iterable, f: (input: A, i: number) => Result): Iterable
} = dual(2, (self: Iterable, f: (input: A, i: number) => Result) => ({
[Symbol.iterator]() {
const iterator = self[Symbol.iterator]()
let i = 0
return {
next() {
const result = iterator.next()
if (result.done) {
return { done: true, value: undefined }
}
const next = f(result.value, i++)
if (R.isSuccess(next)) {
return { done: false, value: next.success }
}
return { done: true, value: undefined }
}
}
}
}))
/**
* Retrieves the `Some` values from an `Iterable` of `Option`s.
*
* @example
* ```ts
* import { Iterable } from "effect"
* import * as Option from "effect/Option"
* import * as assert from "node:assert"
*
* assert.deepStrictEqual(
* Array.from(
* Iterable.getSomes([Option.some(1), Option.none(), Option.some(2)])
* ),
* [1, 2]
* )
* ```
*
* @category filtering
* @since 2.0.0
*/
export const getSomes = (self: Iterable