968 lines
26 KiB
TypeScript
968 lines
26 KiB
TypeScript
/**
|
|
* Mutable lists for collecting ordered values and draining them from the front.
|
|
* A `MutableList<A>` can append values to the end, prepend values to the
|
|
* beginning, take one or more values from the front, inspect its contents as an
|
|
* array, filter values, remove values, and clear itself. All operations update
|
|
* the same list object in place and keep its `length` field current. Taking
|
|
* from an empty list returns the `Empty` symbol.
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
import * as Arr from "./Array.ts"
|
|
|
|
/**
|
|
* A mutable linked list data structure optimized for high-throughput operations.
|
|
* MutableList provides efficient append/prepend operations and is ideal for
|
|
* producer-consumer patterns, queues, and streaming scenarios.
|
|
*
|
|
* **Example** (Creating and consuming a mutable list)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* // Create a mutable list
|
|
* const list: MutableList.MutableList<number> = MutableList.make()
|
|
*
|
|
* // Add elements
|
|
* MutableList.append(list, 1)
|
|
* MutableList.append(list, 2)
|
|
* MutableList.prepend(list, 0)
|
|
*
|
|
* // Access properties
|
|
* console.log(list.length) // 3
|
|
* console.log(list.head?.array) // Contains elements from head bucket
|
|
* console.log(list.tail?.array) // Contains elements from tail bucket
|
|
*
|
|
* // Take elements
|
|
* console.log(MutableList.take(list)) // 0
|
|
* console.log(MutableList.take(list)) // 1
|
|
* console.log(MutableList.take(list)) // 2
|
|
* ```
|
|
*
|
|
* @category models
|
|
* @since 2.0.0
|
|
*/
|
|
export interface MutableList<in out A> {
|
|
head: MutableList.Bucket<A> | undefined
|
|
tail: MutableList.Bucket<A> | undefined
|
|
length: number
|
|
}
|
|
|
|
/**
|
|
* The MutableList namespace contains type definitions and utilities for working
|
|
* with mutable linked lists.
|
|
*
|
|
* **Example** (Typing queue processors)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* // Type annotation using the namespace
|
|
* const processQueue = (queue: MutableList.MutableList<string>) => {
|
|
* while (queue.length > 0) {
|
|
* const item = MutableList.take(queue)
|
|
* if (item !== MutableList.Empty) {
|
|
* console.log("Processing:", item)
|
|
* }
|
|
* }
|
|
* }
|
|
*
|
|
* // Using the namespace for type definitions
|
|
* const createProcessor = <T>(): {
|
|
* queue: MutableList.MutableList<T>
|
|
* add: (item: T) => void
|
|
* process: () => Array<T>
|
|
* } => {
|
|
* const queue = MutableList.make<T>()
|
|
* return {
|
|
* queue,
|
|
* add: (item) => MutableList.append(queue, item),
|
|
* process: () => MutableList.takeAll(queue)
|
|
* }
|
|
* }
|
|
* ```
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
export declare namespace MutableList {
|
|
/**
|
|
* Storage node used by the exposed `head` and `tail` fields of a
|
|
* `MutableList`.
|
|
*
|
|
* **Details**
|
|
*
|
|
* Most code should treat buckets as an implementation detail and use
|
|
* `MutableList` operations such as `append`, `prepend`, and `take` instead
|
|
* of constructing or mutating buckets directly.
|
|
*
|
|
* **Example** (Inspecting buckets)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<number>()
|
|
* MutableList.append(list, 1)
|
|
* MutableList.append(list, 2)
|
|
*
|
|
* // Access bucket information (for debugging or advanced usage)
|
|
* const inspectBucket = (
|
|
* bucket: MutableList.MutableList.Bucket<number> | undefined
|
|
* ) => {
|
|
* if (bucket) {
|
|
* console.log("Bucket array:", bucket.array)
|
|
* console.log("Bucket offset:", bucket.offset)
|
|
* console.log("Bucket mutable:", bucket.mutable)
|
|
* console.log("Has next bucket:", bucket.next !== undefined)
|
|
* }
|
|
* }
|
|
*
|
|
* inspectBucket(list.head)
|
|
* inspectBucket(list.tail)
|
|
* ```
|
|
*
|
|
* @category models
|
|
* @since 4.0.0
|
|
*/
|
|
export interface Bucket<A> {
|
|
readonly array: Array<A>
|
|
mutable: boolean
|
|
offset: number
|
|
next: Bucket<A> | undefined
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Defines the unique symbol used to represent an empty result when taking elements from a MutableList.
|
|
* This symbol is returned by `take` when the list is empty, allowing for safe type checking.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to detect that `take` returned no element before handling the result as a
|
|
* list item.
|
|
*
|
|
* **Example** (Checking for empty results)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<string>()
|
|
*
|
|
* // Take from empty list returns Empty symbol
|
|
* const result = MutableList.take(list)
|
|
* console.log(result === MutableList.Empty) // true
|
|
*
|
|
* // Safe pattern for checking emptiness
|
|
* const processNext = (queue: MutableList.MutableList<string>) => {
|
|
* const item = MutableList.take(queue)
|
|
* if (item === MutableList.Empty) {
|
|
* console.log("Queue is empty")
|
|
* return null
|
|
* }
|
|
* return item.toUpperCase()
|
|
* }
|
|
*
|
|
* // Compare with other empty results
|
|
* MutableList.append(list, "hello")
|
|
* const next = MutableList.take(list)
|
|
* console.log(next !== MutableList.Empty) // true, got "hello"
|
|
*
|
|
* const empty = MutableList.take(list)
|
|
* console.log(empty === MutableList.Empty) // true, list is empty
|
|
* ```
|
|
*
|
|
* @category symbols
|
|
* @since 4.0.0
|
|
*/
|
|
export const Empty: unique symbol = Symbol.for("effect/MutableList/Empty")
|
|
|
|
/**
|
|
* The type of the Empty symbol, used for type checking when taking elements from a MutableList.
|
|
* This provides compile-time safety when checking for empty results.
|
|
*
|
|
* **Example** (Handling empty results type-safely)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<number>()
|
|
*
|
|
* // Type-safe handling of empty results
|
|
* const takeAndDouble = (
|
|
* queue: MutableList.MutableList<number>
|
|
* ): number | null => {
|
|
* const item: number | MutableList.Empty = MutableList.take(queue)
|
|
*
|
|
* if (item === MutableList.Empty) {
|
|
* return null
|
|
* }
|
|
*
|
|
* // TypeScript knows item is number here
|
|
* return item * 2
|
|
* }
|
|
*
|
|
* console.log(takeAndDouble(list)) // null (empty list)
|
|
*
|
|
* MutableList.append(list, 5)
|
|
* console.log(takeAndDouble(list)) // 10
|
|
*
|
|
* // Type guard function
|
|
* const isEmpty = (
|
|
* result: number | MutableList.Empty
|
|
* ): result is MutableList.Empty => {
|
|
* return result === MutableList.Empty
|
|
* }
|
|
*
|
|
* const value = MutableList.take(list)
|
|
* if (isEmpty(value)) {
|
|
* console.log("List is empty")
|
|
* } else {
|
|
* console.log("Got value:", value)
|
|
* }
|
|
* ```
|
|
*
|
|
* @category symbols
|
|
* @since 4.0.0
|
|
*/
|
|
export type Empty = typeof Empty
|
|
|
|
/**
|
|
* Creates an empty MutableList.
|
|
*
|
|
* **Example** (Creating an empty mutable list)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<string>()
|
|
*
|
|
* // Add elements
|
|
* MutableList.append(list, "first")
|
|
* MutableList.append(list, "second")
|
|
* MutableList.prepend(list, "beginning")
|
|
*
|
|
* console.log(list.length) // 3
|
|
*
|
|
* // Take elements in FIFO order (from head)
|
|
* console.log(MutableList.take(list)) // "beginning"
|
|
* console.log(MutableList.take(list)) // "first"
|
|
* console.log(MutableList.take(list)) // "second"
|
|
* ```
|
|
*
|
|
* @category constructors
|
|
* @since 2.0.0
|
|
*/
|
|
export const make = <A>(): MutableList<A> => ({
|
|
head: undefined,
|
|
tail: undefined,
|
|
length: 0
|
|
})
|
|
|
|
const emptyBucket = <A = never>(): MutableList.Bucket<A> => ({
|
|
array: [],
|
|
mutable: true,
|
|
offset: 0,
|
|
next: undefined
|
|
})
|
|
|
|
/**
|
|
* Appends an element to the end of the MutableList.
|
|
* This operation is optimized for high-frequency usage.
|
|
*
|
|
* **Example** (Appending elements)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<number>()
|
|
*
|
|
* // Append elements one by one
|
|
* MutableList.append(list, 1)
|
|
* MutableList.append(list, 2)
|
|
* MutableList.append(list, 3)
|
|
*
|
|
* console.log(list.length) // 3
|
|
*
|
|
* // Elements are taken from head (FIFO)
|
|
* console.log(MutableList.take(list)) // 1
|
|
* console.log(MutableList.take(list)) // 2
|
|
* console.log(MutableList.take(list)) // 3
|
|
*
|
|
* // High-throughput usage
|
|
* for (let i = 0; i < 10000; i++) {
|
|
* MutableList.append(list, i)
|
|
* }
|
|
* ```
|
|
*
|
|
* @category mutations
|
|
* @since 2.0.0
|
|
*/
|
|
export const append = <A>(self: MutableList<A>, message: A): void => {
|
|
if (!self.tail) {
|
|
self.head = self.tail = emptyBucket()
|
|
} else if (!self.tail.mutable) {
|
|
self.tail.next = emptyBucket()
|
|
self.tail = self.tail.next
|
|
}
|
|
self.tail!.array.push(message)
|
|
self.length++
|
|
}
|
|
|
|
/**
|
|
* Prepends an element to the beginning of the MutableList.
|
|
* This operation is optimized for high-frequency usage.
|
|
*
|
|
* **Example** (Prepending elements)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<string>()
|
|
*
|
|
* // Prepend elements (they'll be at the front)
|
|
* MutableList.prepend(list, "third")
|
|
* MutableList.prepend(list, "second")
|
|
* MutableList.prepend(list, "first")
|
|
*
|
|
* console.log(list.length) // 3
|
|
*
|
|
* // Elements taken from head (most recently prepended first)
|
|
* console.log(MutableList.take(list)) // "first"
|
|
* console.log(MutableList.take(list)) // "second"
|
|
* console.log(MutableList.take(list)) // "third"
|
|
*
|
|
* // Use case: priority items or stack-like behavior
|
|
* MutableList.append(list, "normal")
|
|
* MutableList.prepend(list, "priority") // This will be taken first
|
|
* console.log(MutableList.take(list)) // "priority"
|
|
* ```
|
|
*
|
|
* @category mutations
|
|
* @since 2.0.0
|
|
*/
|
|
export const prepend = <A>(self: MutableList<A>, message: A): void => {
|
|
self.head = {
|
|
array: [message],
|
|
mutable: true,
|
|
offset: 0,
|
|
next: self.head
|
|
}
|
|
self.length++
|
|
}
|
|
|
|
/**
|
|
* Prepends all elements from an iterable to the beginning of the MutableList.
|
|
* The elements are added in order, so the first element in the iterable becomes
|
|
* the new head of the list.
|
|
*
|
|
* **Example** (Prepending multiple elements)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<number>()
|
|
* MutableList.append(list, 4)
|
|
* MutableList.append(list, 5)
|
|
*
|
|
* // Prepend multiple elements
|
|
* MutableList.prependAll(list, [1, 2, 3])
|
|
*
|
|
* console.log(list.length) // 5
|
|
*
|
|
* // Elements are taken in order: [1, 2, 3, 4, 5]
|
|
* console.log(MutableList.takeAll(list)) // [1, 2, 3, 4, 5]
|
|
*
|
|
* // Works with any iterable
|
|
* const newList = MutableList.make<string>()
|
|
* MutableList.prependAll(newList, "hello") // Prepends each character
|
|
* console.log(MutableList.takeAll(newList)) // ["h", "e", "l", "l", "o"]
|
|
* ```
|
|
*
|
|
* @category mutations
|
|
* @since 4.0.0
|
|
*/
|
|
export const prependAll = <A>(self: MutableList<A>, messages: Iterable<A>): void =>
|
|
prependAllUnsafe(self, Arr.fromIterable(messages), !Array.isArray(messages))
|
|
|
|
/**
|
|
* Prepends all elements from a ReadonlyArray to the beginning of the MutableList.
|
|
* This is an optimized version that can reuse the array when mutable=true.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use when prepending a trusted array directly is worth the optimized path and
|
|
* you control whether the input may be reused.
|
|
*
|
|
* **Gotchas**
|
|
*
|
|
* When mutable=true, the input array may be modified internally. Only use
|
|
* mutable=true when you control the array lifecycle.
|
|
*
|
|
* **Example** (Prepending arrays with optional mutation)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<number>()
|
|
* MutableList.append(list, 4)
|
|
*
|
|
* // Safe usage (default mutable=false)
|
|
* const items = [1, 2, 3]
|
|
* MutableList.prependAllUnsafe(list, items)
|
|
* console.log(items) // [1, 2, 3] - unchanged
|
|
*
|
|
* // Unsafe but efficient usage (mutable=true)
|
|
* const mutableItems = [10, 20, 30]
|
|
* MutableList.prependAllUnsafe(list, mutableItems, true)
|
|
* // mutableItems may be modified internally for efficiency
|
|
*
|
|
* console.log(MutableList.takeAll(list)) // [10, 20, 30, 1, 2, 3, 4]
|
|
* ```
|
|
*
|
|
* @category mutations
|
|
* @since 4.0.0
|
|
*/
|
|
export const prependAllUnsafe = <A>(self: MutableList<A>, messages: ReadonlyArray<A>, mutable = false): void => {
|
|
self.head = {
|
|
array: messages as Array<A>,
|
|
mutable,
|
|
offset: 0,
|
|
next: self.head
|
|
}
|
|
self.length += self.head.array.length
|
|
}
|
|
|
|
/**
|
|
* Appends all elements from an iterable to the end of the MutableList.
|
|
* Returns the number of elements added.
|
|
*
|
|
* **Example** (Appending multiple elements)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<number>()
|
|
* MutableList.append(list, 1)
|
|
* MutableList.append(list, 2)
|
|
*
|
|
* // Append multiple elements
|
|
* const added = MutableList.appendAll(list, [3, 4, 5])
|
|
* console.log(added) // 3
|
|
* console.log(list.length) // 5
|
|
*
|
|
* // Elements maintain order: [1, 2, 3, 4, 5]
|
|
* console.log(MutableList.takeAll(list)) // [1, 2, 3, 4, 5]
|
|
*
|
|
* // Works with any iterable
|
|
* const newList = MutableList.make<string>()
|
|
* MutableList.appendAll(newList, new Set(["a", "b", "c"]))
|
|
* console.log(MutableList.takeAll(newList)) // ["a", "b", "c"]
|
|
*
|
|
* // Useful for bulk loading
|
|
* const bulkList = MutableList.make<number>()
|
|
* const count = MutableList.appendAll(
|
|
* bulkList,
|
|
* Array.from({ length: 1000 }, (_, i) => i)
|
|
* )
|
|
* console.log(count) // 1000
|
|
* ```
|
|
*
|
|
* @category mutations
|
|
* @since 4.0.0
|
|
*/
|
|
export const appendAll = <A>(self: MutableList<A>, messages: Iterable<A>): number =>
|
|
appendAllUnsafe(self, Arr.fromIterable(messages), !Array.isArray(messages))
|
|
|
|
/**
|
|
* Appends all elements from a ReadonlyArray to the end of the MutableList.
|
|
* This is an optimized version that can reuse the array when mutable=true.
|
|
* Returns the number of elements added.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use when appending a trusted array directly is worth the optimized path and
|
|
* you control whether the input may be reused.
|
|
*
|
|
* **Gotchas**
|
|
*
|
|
* When mutable=true, the input array may be modified internally. Only use
|
|
* mutable=true when you control the array lifecycle.
|
|
*
|
|
* **Example** (Appending arrays with optional mutation)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<number>()
|
|
* MutableList.append(list, 1)
|
|
*
|
|
* // Safe usage (default mutable=false)
|
|
* const items = [2, 3, 4]
|
|
* const added = MutableList.appendAllUnsafe(list, items)
|
|
* console.log(added) // 3
|
|
* console.log(items) // [2, 3, 4] - unchanged
|
|
*
|
|
* // Unsafe but efficient usage (mutable=true)
|
|
* const mutableItems = [5, 6, 7]
|
|
* MutableList.appendAllUnsafe(list, mutableItems, true)
|
|
* // mutableItems may be modified internally for efficiency
|
|
*
|
|
* console.log(MutableList.takeAll(list)) // [1, 2, 3, 4, 5, 6, 7]
|
|
*
|
|
* // High-performance bulk operations
|
|
* const bigArray = new Array(10000).fill(0).map((_, i) => i)
|
|
* MutableList.appendAllUnsafe(list, bigArray, true) // Very efficient
|
|
* ```
|
|
*
|
|
* @category mutations
|
|
* @since 4.0.0
|
|
*/
|
|
export const appendAllUnsafe = <A>(self: MutableList<A>, messages: ReadonlyArray<A>, mutable = false): number => {
|
|
if (messages.length === 0) {
|
|
return 0
|
|
}
|
|
const chunk: MutableList.Bucket<A> = {
|
|
array: messages as Array<A>,
|
|
mutable,
|
|
offset: 0,
|
|
next: undefined
|
|
}
|
|
if (self.head) {
|
|
self.tail = self.tail!.next = chunk
|
|
} else {
|
|
self.head = self.tail = chunk
|
|
}
|
|
self.length += messages.length
|
|
return messages.length
|
|
}
|
|
|
|
/**
|
|
* Removes all elements from the MutableList, resetting it to an empty state.
|
|
* This operation is highly optimized and releases all internal memory.
|
|
*
|
|
* **Example** (Clearing a mutable list)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<number>()
|
|
* MutableList.appendAll(list, [1, 2, 3, 4, 5])
|
|
*
|
|
* console.log(list.length) // 5
|
|
*
|
|
* // Clear all elements
|
|
* MutableList.clear(list)
|
|
*
|
|
* console.log(list.length) // 0
|
|
* console.log(MutableList.take(list)) // Empty
|
|
*
|
|
* // Can still use the list after clearing
|
|
* MutableList.append(list, 42)
|
|
* console.log(list.length) // 1
|
|
*
|
|
* // Useful for resetting queues or buffers
|
|
* function resetBuffer<T>(buffer: MutableList.MutableList<T>) {
|
|
* MutableList.clear(buffer)
|
|
* console.log("Buffer cleared and ready for reuse")
|
|
* }
|
|
* ```
|
|
*
|
|
* @category mutations
|
|
* @since 4.0.0
|
|
*/
|
|
export const clear = <A>(self: MutableList<A>): void => {
|
|
self.head = self.tail = undefined
|
|
self.length = 0
|
|
}
|
|
|
|
/**
|
|
* Takes up to N elements from the beginning of the MutableList and returns them as an array.
|
|
* The taken elements are removed from the list. This operation is optimized for performance
|
|
* and includes zero-copy optimizations when possible.
|
|
*
|
|
* **Example** (Taking batches)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<number>()
|
|
* MutableList.appendAll(list, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
|
*
|
|
* console.log(list.length) // 10
|
|
*
|
|
* // Take first 3 elements
|
|
* const first3 = MutableList.takeN(list, 3)
|
|
* console.log(first3) // [1, 2, 3]
|
|
* console.log(list.length) // 7
|
|
*
|
|
* // Take more than available
|
|
* const remaining = MutableList.takeN(list, 20)
|
|
* console.log(remaining) // [4, 5, 6, 7, 8, 9, 10]
|
|
* console.log(list.length) // 0
|
|
*
|
|
* // Take from empty list
|
|
* const empty = MutableList.takeN(list, 5)
|
|
* console.log(empty) // []
|
|
*
|
|
* // Batch processing pattern
|
|
* const queue = MutableList.make<string>()
|
|
* MutableList.appendAll(queue, ["task1", "task2", "task3", "task4", "task5"])
|
|
*
|
|
* while (queue.length > 0) {
|
|
* const batch = MutableList.takeN(queue, 2) // Process 2 at a time
|
|
* console.log("Processing batch:", batch)
|
|
* }
|
|
* ```
|
|
*
|
|
* @category elements
|
|
* @since 4.0.0
|
|
*/
|
|
export const takeN = <A>(self: MutableList<A>, n: number): Array<A> => {
|
|
if (n <= 0 || !self.head) return []
|
|
n = Math.min(n, self.length)
|
|
if (n === self.length && self.head?.offset === 0 && !self.head.next) {
|
|
const array = self.head.array
|
|
clear(self)
|
|
return array
|
|
}
|
|
const array = new Array<A>(n)
|
|
let index = 0
|
|
let chunk: MutableList.Bucket<A> | undefined = self.head
|
|
while (chunk) {
|
|
while (chunk.offset < chunk.array.length) {
|
|
array[index++] = chunk.array[chunk.offset]
|
|
if (chunk.mutable) chunk.array[chunk.offset] = undefined as any
|
|
chunk.offset++
|
|
if (index === n) {
|
|
self.head = chunk
|
|
self.length -= n
|
|
if (self.length === 0) clear(self)
|
|
return array
|
|
}
|
|
}
|
|
chunk = chunk.next
|
|
}
|
|
clear(self)
|
|
return array
|
|
}
|
|
|
|
/**
|
|
* Removes up to `n` elements from the beginning of the `MutableList` without
|
|
* returning them.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to discard a bounded number of values from the head of a `MutableList`
|
|
* when the removed values are not needed.
|
|
*
|
|
* **Details**
|
|
*
|
|
* If `n` is less than or equal to zero, or the list is empty, the list is left
|
|
* unchanged. If `n` is greater than or equal to the current length, the list is
|
|
* cleared.
|
|
*
|
|
* @see {@link takeN} for removing up to `n` values and returning them as an array
|
|
* @see {@link clear} for removing every value from the list
|
|
*
|
|
* @category elements
|
|
* @since 4.0.0
|
|
*/
|
|
export const takeNVoid = <A>(self: MutableList<A>, n: number): void => {
|
|
if (n <= 0 || !self.head) return
|
|
n = Math.min(n, self.length)
|
|
if (n === self.length && self.head?.offset === 0 && !self.head.next) {
|
|
clear(self)
|
|
return
|
|
}
|
|
let count = 0
|
|
let chunk: MutableList.Bucket<A> | undefined = self.head
|
|
while (chunk) {
|
|
const size = chunk.array.length - chunk.offset
|
|
if (count + size > n) {
|
|
chunk.offset += n - count
|
|
self.head = chunk
|
|
self.length -= n
|
|
return
|
|
}
|
|
count += size
|
|
chunk = chunk.next
|
|
}
|
|
clear(self)
|
|
return
|
|
}
|
|
|
|
/**
|
|
* Takes all elements from the MutableList and returns them as an array.
|
|
* The list becomes empty after this operation. This is equivalent to takeN(list, list.length).
|
|
*
|
|
* **Example** (Draining all elements)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<string>()
|
|
* MutableList.appendAll(list, ["apple", "banana", "cherry"])
|
|
*
|
|
* console.log(list.length) // 3
|
|
*
|
|
* // Take all elements
|
|
* const allItems = MutableList.takeAll(list)
|
|
* console.log(allItems) // ["apple", "banana", "cherry"]
|
|
* console.log(list.length) // 0
|
|
*
|
|
* // Useful for converting to array and clearing
|
|
* const queue = MutableList.make<number>()
|
|
* MutableList.appendAll(queue, [1, 2, 3, 4, 5])
|
|
*
|
|
* const snapshot = MutableList.takeAll(queue)
|
|
* console.log("Queue contents:", snapshot)
|
|
* console.log("Queue is now empty:", queue.length === 0)
|
|
*
|
|
* // Drain pattern for processing
|
|
* function drainAndProcess<T>(
|
|
* list: MutableList.MutableList<T>,
|
|
* processor: (items: Array<T>) => void
|
|
* ) {
|
|
* if (list.length > 0) {
|
|
* const items = MutableList.takeAll(list)
|
|
* processor(items)
|
|
* }
|
|
* }
|
|
* ```
|
|
*
|
|
* @category elements
|
|
* @since 4.0.0
|
|
*/
|
|
export const takeAll = <A>(self: MutableList<A>): Array<A> => takeN(self, self.length)
|
|
|
|
/**
|
|
* Takes a single element from the beginning of the MutableList.
|
|
* Returns the element if available, or the Empty symbol if the list is empty.
|
|
* The taken element is removed from the list.
|
|
*
|
|
* **Example** (Taking one element)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<string>()
|
|
* MutableList.appendAll(list, ["first", "second", "third"])
|
|
*
|
|
* // Take elements one by one
|
|
* console.log(MutableList.take(list)) // "first"
|
|
* console.log(list.length) // 2
|
|
*
|
|
* console.log(MutableList.take(list)) // "second"
|
|
* console.log(MutableList.take(list)) // "third"
|
|
* console.log(list.length) // 0
|
|
*
|
|
* // Take from empty list
|
|
* console.log(MutableList.take(list)) // Empty symbol
|
|
*
|
|
* // Check for empty using the Empty symbol
|
|
* const result = MutableList.take(list)
|
|
* if (result === MutableList.Empty) {
|
|
* console.log("List is empty")
|
|
* } else {
|
|
* console.log("Got element:", result)
|
|
* }
|
|
*
|
|
* // Consumer pattern
|
|
* function processNext<T>(
|
|
* queue: MutableList.MutableList<T>,
|
|
* processor: (item: T) => void
|
|
* ): boolean {
|
|
* const item = MutableList.take(queue)
|
|
* if (item !== MutableList.Empty) {
|
|
* processor(item)
|
|
* return true
|
|
* }
|
|
* return false
|
|
* }
|
|
* ```
|
|
*
|
|
* @category elements
|
|
* @since 4.0.0
|
|
*/
|
|
export const take = <A>(self: MutableList<A>): Empty | A => {
|
|
if (!self.head) return Empty
|
|
const message = self.head.array[self.head.offset]
|
|
if (self.head.mutable) self.head.array[self.head.offset] = undefined as any
|
|
self.head.offset++
|
|
self.length--
|
|
if (self.head.offset === self.head.array.length) {
|
|
if (self.head.next) {
|
|
self.head = self.head.next
|
|
} else {
|
|
clear(self)
|
|
}
|
|
}
|
|
return message
|
|
}
|
|
|
|
/**
|
|
* Copies up to `n` elements from the beginning of the `MutableList` into a new
|
|
* array without modifying the list.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use when you need to inspect or snapshot a bounded prefix of the list without
|
|
* consuming it.
|
|
*
|
|
* @see {@link takeN} for removing up to `n` values and returning them as an array
|
|
*
|
|
* @category elements
|
|
* @since 4.0.0
|
|
*/
|
|
export const toArrayN = <A>(self: MutableList<A>, n: number): Array<A> => {
|
|
const length = Math.min(n, self.length)
|
|
const out = new Array<A>(length)
|
|
let index = 0
|
|
let bucket = self.head
|
|
while (bucket) {
|
|
for (let i = bucket.offset; i < bucket.array.length; i++) {
|
|
out[index++] = bucket.array[i]
|
|
if (index === length) return out
|
|
}
|
|
bucket = bucket.next
|
|
}
|
|
return out
|
|
}
|
|
|
|
/**
|
|
* Copies all current elements of the `MutableList` into a new array without
|
|
* modifying the list.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use when you need a snapshot of all current elements while keeping the list
|
|
* unchanged.
|
|
*
|
|
* @see {@link takeAll} for converting all elements to an array and clearing the list
|
|
*
|
|
* @category elements
|
|
* @since 4.0.0
|
|
*/
|
|
export const toArray = <A>(self: MutableList<A>): Array<A> => toArrayN(self, self.length)
|
|
|
|
/**
|
|
* Filters the MutableList in place, keeping only elements that satisfy the predicate.
|
|
* This operation modifies the list and rebuilds its internal structure for efficiency.
|
|
*
|
|
* **Example** (Filtering in place)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<number>()
|
|
* MutableList.appendAll(list, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
|
*
|
|
* console.log(list.length) // 10
|
|
*
|
|
* // Keep only even numbers
|
|
* MutableList.filter(list, (n) => n % 2 === 0)
|
|
*
|
|
* console.log(MutableList.takeAll(list)) // [2, 4, 6, 8, 10]
|
|
*
|
|
* // Filter with index
|
|
* const indexed = MutableList.make<string>()
|
|
* MutableList.appendAll(indexed, ["a", "b", "c", "d", "e"])
|
|
*
|
|
* // Keep elements at even indices
|
|
* MutableList.filter(indexed, (value, index) => index % 2 === 0)
|
|
* console.log(MutableList.takeAll(indexed)) // ["a", "c", "e"]
|
|
*
|
|
* // Real-world example: filtering a log queue
|
|
* const logs = MutableList.make<{ level: string; message: string }>()
|
|
* MutableList.appendAll(logs, [
|
|
* { level: "INFO", message: "App started" },
|
|
* { level: "ERROR", message: "Connection failed" },
|
|
* { level: "DEBUG", message: "Cache hit" },
|
|
* { level: "ERROR", message: "Timeout" }
|
|
* ])
|
|
*
|
|
* // Keep only errors
|
|
* MutableList.filter(logs, (log) => log.level === "ERROR")
|
|
* console.log(MutableList.takeAll(logs).map((log) => log.message)) // ["Connection failed", "Timeout"]
|
|
* ```
|
|
*
|
|
* @category mutations
|
|
* @since 4.0.0
|
|
*/
|
|
export const filter = <A>(self: MutableList<A>, f: (value: A, i: number) => boolean): void => {
|
|
const array: Array<A> = []
|
|
let chunk: MutableList.Bucket<A> | undefined = self.head
|
|
while (chunk) {
|
|
for (let i = chunk.offset; i < chunk.array.length; i++) {
|
|
if (f(chunk.array[i], i)) {
|
|
array.push(chunk.array[i])
|
|
}
|
|
}
|
|
chunk = chunk.next
|
|
}
|
|
self.head = self.tail = {
|
|
array,
|
|
mutable: true,
|
|
offset: 0,
|
|
next: undefined
|
|
}
|
|
self.length = array.length
|
|
}
|
|
|
|
/**
|
|
* Removes all occurrences of a value from the `MutableList` using JavaScript
|
|
* strict equality semantics.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use when in-place removal should use JavaScript identity/strict equality
|
|
* rather than Effect structural equality.
|
|
*
|
|
* **Details**
|
|
*
|
|
* The list is modified in place.
|
|
*
|
|
* **Gotchas**
|
|
*
|
|
* Values are compared with `!==`, so this does not use Effect structural
|
|
* equality.
|
|
*
|
|
* **Example** (Removing matching values)
|
|
*
|
|
* ```ts
|
|
* import { MutableList } from "effect"
|
|
*
|
|
* const list = MutableList.make<string>()
|
|
* MutableList.appendAll(list, ["apple", "banana", "apple", "cherry", "apple"])
|
|
*
|
|
* console.log(list.length) // 5
|
|
*
|
|
* // Remove all occurrences of "apple"
|
|
* MutableList.remove(list, "apple")
|
|
*
|
|
* console.log(MutableList.takeAll(list)) // ["banana", "cherry"]
|
|
*
|
|
* // Remove non-existent value (no effect)
|
|
* const colors = MutableList.make<string>()
|
|
* MutableList.appendAll(colors, ["red", "blue"])
|
|
* MutableList.remove(colors, "green")
|
|
* console.log(MutableList.takeAll(colors)) // ["red", "blue"]
|
|
*
|
|
* // Real-world example: removing completed tasks
|
|
* const tasks = MutableList.make<{ id: number; status: string }>()
|
|
* MutableList.appendAll(tasks, [
|
|
* { id: 1, status: "pending" },
|
|
* { id: 2, status: "completed" },
|
|
* { id: 3, status: "pending" },
|
|
* { id: 4, status: "completed" }
|
|
* ])
|
|
*
|
|
* // Remove completed tasks by filtering status
|
|
* MutableList.filter(tasks, (task) => task.status !== "completed")
|
|
* console.log(MutableList.takeAll(tasks).map((task) => task.id)) // [1, 3]
|
|
* ```
|
|
*
|
|
* @category mutations
|
|
* @since 4.0.0
|
|
*/
|
|
export const remove = <A>(self: MutableList<A>, value: A): void => filter(self, (v) => v !== value)
|