- Introduction & Core Concepts
- Basic Usage Patterns
- Real-World Examples
- Advanced Features Deep Dive
- Practical Patterns & Best Practices
- Integration Examples
When building applications that require frequent insertion and deletion at arbitrary positions, JavaScript arrays become a performance bottleneck. Traditional array operations like inserting or removing elements from the middle require shifting all subsequent elements, leading to O(n) complexity:
// Traditional approach - inefficient insertion/deletion with arrays
class TaskManager {
private tasks: Task[] = []
// O(n) - must shift all elements after insertion point
insertTaskAt(index: number, task: Task): void {
this.tasks.splice(index, 0, task)
}
// O(n) - must shift all elements after deletion point
removeTaskAt(index: number): Task | undefined {
return this.tasks.splice(index, 1)[0]
}
// O(n) - linear search to find and remove
removeTask(taskId: string): boolean {
const index = this.tasks.findIndex(t => t.id === taskId)
if (index >= 0) {
this.tasks.splice(index, 1)
return true
}
return false
}
// Moving tasks around requires multiple O(n) operations
moveTask(fromIndex: number, toIndex: number): void {
const [task] = this.tasks.splice(fromIndex, 1) // O(n)
this.tasks.splice(toIndex, 0, task) // O(n)
}
}
// Implementing undo/redo becomes complex and memory-intensive
class UndoableEditor {
private content: string[] = []
private history: string[][] = []
// O(n) memory copy for each state save
saveState(): void {
this.history.push([...this.content]) // Full array copy
}
// O(n) insertion with expensive state management
insertLineAt(index: number, line: string): void {
this.saveState()
this.content.splice(index, 0, line)
}
// Memory grows quickly with large documents
undo(): void {
const previousState = this.history.pop()
if (previousState) {
this.content = previousState // Another O(n) copy
}
}
}This approach leads to:
- Performance degradation - O(n) complexity for insertions, deletions, and moves
- Memory inefficiency - Array operations require copying and shifting elements
- Complex state management - Maintaining history or undo operations is expensive
- Poor scalability - Performance degrades linearly with data size
Effect's MutableList provides a doubly-linked list implementation that offers O(1) insertion and deletion at any position when you have a reference to the node, making it ideal for scenarios requiring frequent modifications:
import { MutableList } from "effect"
// Efficient task management with MutableList
class EfficientTaskManager {
private tasks = MutableList.empty<Task>()
private taskNodes = new Map<string, Task>()
// O(1) - append to end
addTask(task: Task): void {
MutableList.append(this.tasks, task)
this.taskNodes.set(task.id, task)
}
// O(1) - prepend to beginning
addUrgentTask(task: Task): void {
MutableList.prepend(this.tasks, task)
this.taskNodes.set(task.id, task)
}
// O(1) - remove from either end
removeFirstTask(): Task | undefined {
return MutableList.shift(this.tasks)
}
removeLastTask(): Task | undefined {
return MutableList.pop(this.tasks)
}
// Efficient iteration without array copies
forEachTask(callback: (task: Task) => void): void {
MutableList.forEach(this.tasks, callback)
}
// O(1) reset for bulk operations
clearAllTasks(): void {
MutableList.reset(this.tasks)
this.taskNodes.clear()
}
}
// Memory-efficient undo system
class EfficientEditor {
private lines = MutableList.empty<string>()
private snapshots: MutableList<string>[] = []
// O(1) line operations
addLine(content: string): void {
MutableList.append(this.lines, content)
}
insertLineAtBeginning(content: string): void {
MutableList.prepend(this.lines, content)
}
// Efficient state snapshots - only store references
saveSnapshot(): void {
// Create new list from current state
const snapshot = MutableList.fromIterable(this.lines)
this.snapshots.push(snapshot)
}
// Quick restoration without expensive copies
restoreSnapshot(): void {
const snapshot = this.snapshots.pop()
if (snapshot) {
MutableList.reset(this.lines)
MutableList.forEach(snapshot, line => MutableList.append(this.lines, line))
}
}
}Doubly-Linked Structure: Each element maintains references to both previous and next nodes, enabling efficient bidirectional traversal and modification.
O(1) Head/Tail Operations: Adding or removing elements from either end of the list is constant time.
Mutable by Design: Unlike immutable lists, MutableList is designed for scenarios where mutation is necessary for performance.
Iterator Support: Implements JavaScript's Iterable interface for seamless integration with for-of loops and array utilities.
import { MutableList } from "effect"
// Creating empty lists
const numbers = MutableList.empty<number>()
const names = MutableList.empty<string>()
// Creating from existing data
const fruits = MutableList.fromIterable(["apple", "banana", "cherry"])
const priorities = MutableList.make(1, 2, 3, 4, 5)
// Building lists incrementally
const shoppingList = MutableList.empty<string>()
MutableList.append(shoppingList, "milk")
MutableList.append(shoppingList, "bread")
MutableList.prepend(shoppingList, "urgent: medicine") // Goes to front// Queue-like behavior (FIFO)
const taskQueue = MutableList.empty<Task>()
// Add to back of queue
MutableList.append(taskQueue, { id: "1", priority: "normal" })
MutableList.append(taskQueue, { id: "2", priority: "normal" })
// Process from front of queue
while (!MutableList.isEmpty(taskQueue)) {
const nextTask = MutableList.shift(taskQueue) // Remove from front
if (nextTask) {
processTask(nextTask)
}
}
// Stack-like behavior (LIFO)
const undoStack = MutableList.empty<Action>()
// Push operations
MutableList.append(undoStack, { type: "insert", data: "hello" })
MutableList.append(undoStack, { type: "delete", data: "world" })
// Pop operations
const lastAction = MutableList.pop(undoStack) // Remove from back
if (lastAction) {
undoAction(lastAction)
}const inventory = MutableList.make("widgets", "gadgets", "tools")
// Safe head/tail access
const firstItem = MutableList.head(inventory) // "widgets" | undefined
const lastItem = MutableList.tail(inventory) // "tools" | undefined
// Length and emptiness checks
const itemCount = MutableList.length(inventory) // 3
const isEmpty = MutableList.isEmpty(inventory) // false
// Efficient iteration
MutableList.forEach(inventory, (item) => {
console.log(`Inventory item: ${item}`)
})
// JavaScript iteration protocols
for (const item of inventory) {
console.log(`Processing: ${item}`)
}
// Convert to array when needed (creates new array)
const inventoryArray = Array.from(inventory)Building a chat application that maintains a scrollable message history with efficient insertion and cleanup:
import { MutableList, Effect } from "effect"
interface ChatMessage {
id: string
userId: string
content: string
timestamp: Date
edited?: boolean
}
class ChatMessageBuffer {
private messages = MutableList.empty<ChatMessage>()
private readonly maxMessages: number
constructor(maxMessages: number = 1000) {
this.maxMessages = maxMessages
}
// O(1) - Add new message to end
addMessage(message: ChatMessage): Effect.Effect<void> {
return Effect.gen(function* () {
MutableList.append(this.messages, message)
// Maintain buffer size - O(1) removal from front
if (MutableList.length(this.messages) > this.maxMessages) {
MutableList.shift(this.messages)
}
}.bind(this))
}
// O(1) - Add system message to front (urgent notifications)
addSystemMessage(message: ChatMessage): Effect.Effect<void> {
return Effect.gen(function* () {
MutableList.prepend(this.messages, message)
// Remove oldest message if needed
if (MutableList.length(this.messages) > this.maxMessages) {
MutableList.pop(this.messages)
}
}.bind(this))
}
// Efficient message processing without array copies
processRecentMessages(processor: (msg: ChatMessage) => Effect.Effect<void>): Effect.Effect<void> {
return Effect.gen(function* () {
let processed = 0
const maxRecent = 50
for (const message of this.messages) {
if (processed >= maxRecent) break
yield* processor(message)
processed++
}
}.bind(this))
}
// Get messages for UI rendering
getRecentMessages(count: number): ChatMessage[] {
const result: ChatMessage[] = []
let collected = 0
for (const message of this.messages) {
if (collected >= count) break
result.push(message)
collected++
}
return result
}
// Efficient cleanup for memory management
clearOldMessages(): Effect.Effect<number> {
return Effect.gen(function* () {
const cutoffTime = new Date(Date.now() - 24 * 60 * 60 * 1000) // 24 hours ago
let removedCount = 0
// Remove old messages from the front
while (!MutableList.isEmpty(this.messages)) {
const oldest = MutableList.head(this.messages)
if (oldest && oldest.timestamp < cutoffTime) {
MutableList.shift(this.messages)
removedCount++
} else {
break
}
}
return removedCount
}.bind(this))
}
}
// Usage in chat application
const createChatService = Effect.gen(function* () {
const messageBuffer = new ChatMessageBuffer(500)
const addUserMessage = (userId: string, content: string) =>
messageBuffer.addMessage({
id: crypto.randomUUID(),
userId,
content,
timestamp: new Date()
})
const addSystemAlert = (content: string) =>
messageBuffer.addSystemMessage({
id: crypto.randomUUID(),
userId: "system",
content,
timestamp: new Date()
})
return { messageBuffer, addUserMessage, addSystemAlert }
})Implementing a task scheduler that efficiently manages task queues with priority-based insertion:
import { MutableList, Effect, Option } from "effect"
interface ScheduledTask {
id: string
priority: "low" | "normal" | "high" | "urgent"
execute: () => Effect.Effect<void>
createdAt: Date
retryCount: number
}
class PriorityTaskScheduler {
private urgentTasks = MutableList.empty<ScheduledTask>()
private highTasks = MutableList.empty<ScheduledTask>()
private normalTasks = MutableList.empty<ScheduledTask>()
private lowTasks = MutableList.empty<ScheduledTask>()
private isProcessing = false
// O(1) task insertion based on priority
scheduleTask(task: ScheduledTask): Effect.Effect<void> {
return Effect.gen(function* () {
switch (task.priority) {
case "urgent":
MutableList.append(this.urgentTasks, task)
break
case "high":
MutableList.append(this.highTasks, task)
break
case "normal":
MutableList.append(this.normalTasks, task)
break
case "low":
MutableList.append(this.lowTasks, task)
break
}
// Start processing if not already running
if (!this.isProcessing) {
yield* this.startProcessing()
}
}.bind(this))
}
// Add urgent task to front of urgent queue
scheduleUrgentTask(task: ScheduledTask): Effect.Effect<void> {
return Effect.gen(function* () {
const urgentTask = { ...task, priority: "urgent" as const }
MutableList.prepend(this.urgentTasks, urgentTask)
if (!this.isProcessing) {
yield* this.startProcessing()
}
}.bind(this))
}
// O(1) task retrieval following priority order
private getNextTask(): Option.Option<ScheduledTask> {
// Check urgent tasks first
if (!MutableList.isEmpty(this.urgentTasks)) {
const task = MutableList.shift(this.urgentTasks)
return task ? Option.some(task) : Option.none()
}
// Then high priority
if (!MutableList.isEmpty(this.highTasks)) {
const task = MutableList.shift(this.highTasks)
return task ? Option.some(task) : Option.none()
}
// Then normal priority
if (!MutableList.isEmpty(this.normalTasks)) {
const task = MutableList.shift(this.normalTasks)
return task ? Option.some(task) : Option.none()
}
// Finally low priority
if (!MutableList.isEmpty(this.lowTasks)) {
const task = MutableList.shift(this.lowTasks)
return task ? Option.some(task) : Option.none()
}
return Option.none()
}
private startProcessing(): Effect.Effect<void> {
return Effect.gen(function* () {
this.isProcessing = true
while (true) {
const nextTaskOption = this.getNextTask()
if (Option.isNone(nextTaskOption)) {
break // No more tasks
}
const task = nextTaskOption.value
// Execute task with retry logic
const result = yield* Effect.gen(function* () {
return yield* task.execute()
}).pipe(
Effect.catchAll((error) => this.handleTaskFailure(task, error))
)
}
this.isProcessing = false
}.bind(this))
}
private handleTaskFailure(task: ScheduledTask, error: unknown): Effect.Effect<void> {
return Effect.gen(function* () {
if (task.retryCount < 3) {
// Retry with backoff - add to appropriate queue
const retryTask = {
...task,
retryCount: task.retryCount + 1
}
yield* Effect.sleep(`${Math.pow(2, task.retryCount)} seconds`)
// Demote priority for retries
const newPriority = task.priority === "urgent" ? "high" :
task.priority === "high" ? "normal" :
task.priority === "normal" ? "low" : "low"
yield* this.scheduleTask({ ...retryTask, priority: newPriority })
} else {
// Log failure and move on
console.error(`Task ${task.id} failed after ${task.retryCount} retries:`, error)
}
}.bind(this))
}
// Get current queue status
getQueueStatus(): Effect.Effect<{
urgent: number
high: number
normal: number
low: number
total: number
}> {
return Effect.succeed({
urgent: MutableList.length(this.urgentTasks),
high: MutableList.length(this.highTasks),
normal: MutableList.length(this.normalTasks),
low: MutableList.length(this.lowTasks),
total: MutableList.length(this.urgentTasks) +
MutableList.length(this.highTasks) +
MutableList.length(this.normalTasks) +
MutableList.length(this.lowTasks)
})
}
// Clear all tasks of a specific priority
clearPriorityQueue(priority: ScheduledTask["priority"]): Effect.Effect<number> {
return Effect.gen(function* () {
let clearedCount = 0
switch (priority) {
case "urgent":
clearedCount = MutableList.length(this.urgentTasks)
MutableList.reset(this.urgentTasks)
break
case "high":
clearedCount = MutableList.length(this.highTasks)
MutableList.reset(this.highTasks)
break
case "normal":
clearedCount = MutableList.length(this.normalTasks)
MutableList.reset(this.normalTasks)
break
case "low":
clearedCount = MutableList.length(this.lowTasks)
MutableList.reset(this.lowTasks)
break
}
return clearedCount
}.bind(this))
}
}
// Usage example
const taskSchedulerExample = Effect.gen(function* () {
const scheduler = new PriorityTaskScheduler()
// Schedule various tasks
yield* scheduler.scheduleTask({
id: "daily-backup",
priority: "low",
execute: () => Effect.succeed(console.log("Running daily backup")),
createdAt: new Date(),
retryCount: 0
})
yield* scheduler.scheduleUrgentTask({
id: "security-alert",
priority: "urgent",
execute: () => Effect.succeed(console.log("Processing security alert")),
createdAt: new Date(),
retryCount: 0
})
// Check queue status
const status = yield* scheduler.getQueueStatus()
console.log("Queue status:", status)
})Implementing an efficient undo/redo system that manages document state changes without expensive memory copies:
import { MutableList, Effect, Option } from "effect"
interface DocumentChange {
id: string
type: "insert" | "delete" | "replace"
position: number
content: string
previousContent?: string
timestamp: Date
}
interface DocumentState {
content: string
cursorPosition: number
version: number
}
class DocumentUndoRedoManager {
private undoStack = MutableList.empty<DocumentChange>()
private redoStack = MutableList.empty<DocumentChange>()
private currentState: DocumentState
private maxHistorySize: number
constructor(initialContent: string = "", maxHistorySize: number = 100) {
this.currentState = {
content: initialContent,
cursorPosition: 0,
version: 0
}
this.maxHistorySize = maxHistorySize
}
// O(1) - Add change to undo stack
recordChange(change: DocumentChange): Effect.Effect<void> {
return Effect.gen(function* () {
// Clear redo stack when new change is made
MutableList.reset(this.redoStack)
// Add to undo stack
MutableList.append(this.undoStack, change)
// Maintain history size limit
if (MutableList.length(this.undoStack) > this.maxHistorySize) {
MutableList.shift(this.undoStack) // Remove oldest
}
// Apply change to current state
yield* this.applyChange(change)
}.bind(this))
}
// O(1) - Undo last change
undo(): Effect.Effect<Option.Option<DocumentState>> {
return Effect.gen(function* () {
const lastChange = MutableList.pop(this.undoStack)
if (!lastChange) {
return Option.none()
}
// Move change to redo stack
MutableList.append(this.redoStack, lastChange)
// Reverse the change
yield* this.reverseChange(lastChange)
return Option.some({ ...this.currentState })
}.bind(this))
}
// O(1) - Redo last undone change
redo(): Effect.Effect<Option.Option<DocumentState>> {
return Effect.gen(function* () {
const lastUndone = MutableList.pop(this.redoStack)
if (!lastUndone) {
return Option.none()
}
// Move back to undo stack
MutableList.append(this.undoStack, lastUndone)
// Reapply the change
yield* this.applyChange(lastUndone)
return Option.some({ ...this.currentState })
}.bind(this))
}
private applyChange(change: DocumentChange): Effect.Effect<void> {
return Effect.gen(function* () {
const { content } = this.currentState
switch (change.type) {
case "insert":
this.currentState.content =
content.slice(0, change.position) +
change.content +
content.slice(change.position)
this.currentState.cursorPosition = change.position + change.content.length
break
case "delete":
this.currentState.content =
content.slice(0, change.position) +
content.slice(change.position + change.content.length)
this.currentState.cursorPosition = change.position
break
case "replace":
this.currentState.content =
content.slice(0, change.position) +
change.content +
content.slice(change.position + (change.previousContent?.length || 0))
this.currentState.cursorPosition = change.position + change.content.length
break
}
this.currentState.version++
}.bind(this))
}
private reverseChange(change: DocumentChange): Effect.Effect<void> {
return Effect.gen(function* () {
switch (change.type) {
case "insert":
// Reverse insert by deleting
yield* this.applyChange({
...change,
type: "delete",
content: change.content
})
break
case "delete":
// Reverse delete by inserting
yield* this.applyChange({
...change,
type: "insert",
content: change.content
})
break
case "replace":
// Reverse replace by restoring previous content
yield* this.applyChange({
...change,
type: "replace",
content: change.previousContent || "",
previousContent: change.content
})
break
}
}.bind(this))
}
// Batch operations for complex edits
recordBatchChanges(changes: DocumentChange[]): Effect.Effect<void> {
return Effect.gen(function* () {
// Create a composite change for undo purposes
const batchChange: DocumentChange = {
id: crypto.randomUUID(),
type: "replace",
position: Math.min(...changes.map(c => c.position)),
content: this.currentState.content, // Will be updated after applying all changes
previousContent: this.currentState.content,
timestamp: new Date()
}
// Apply all changes
for (const change of changes) {
yield* this.applyChange(change)
}
// Update batch change with final content
batchChange.content = this.currentState.content
// Record the batch as a single undoable operation
MutableList.append(this.undoStack, batchChange)
MutableList.reset(this.redoStack)
if (MutableList.length(this.undoStack) > this.maxHistorySize) {
MutableList.shift(this.undoStack)
}
}.bind(this))
}
// Get history information
getHistoryInfo(): Effect.Effect<{
canUndo: boolean
canRedo: boolean
undoCount: number
redoCount: number
currentVersion: number
}> {
return Effect.succeed({
canUndo: !MutableList.isEmpty(this.undoStack),
canRedo: !MutableList.isEmpty(this.redoStack),
undoCount: MutableList.length(this.undoStack),
redoCount: MutableList.length(this.redoStack),
currentVersion: this.currentState.version
})
}
getCurrentState(): DocumentState {
return { ...this.currentState }
}
// Clear all history
clearHistory(): Effect.Effect<void> {
return Effect.gen(function* () {
MutableList.reset(this.undoStack)
MutableList.reset(this.redoStack)
}.bind(this))
}
}
// Usage in document editor
const documentEditorExample = Effect.gen(function* () {
const undoRedoManager = new DocumentUndoRedoManager("Hello World")
// User types some text
yield* undoRedoManager.recordChange({
id: "1",
type: "insert",
position: 11,
content: "!",
timestamp: new Date()
})
// User deletes some text
yield* undoRedoManager.recordChange({
id: "2",
type: "delete",
position: 6,
content: "World",
timestamp: new Date()
})
console.log("Current state:", undoRedoManager.getCurrentState())
// Undo the last change
const undoResult = yield* undoRedoManager.undo()
if (Option.isSome(undoResult)) {
console.log("After undo:", undoResult.value)
}
// Check what operations are available
const historyInfo = yield* undoRedoManager.getHistoryInfo()
console.log("History info:", historyInfo)
})MutableList implements JavaScript's Iterable protocol, providing efficient iteration without creating intermediate arrays:
import { MutableList } from "effect"
const processLogEntries = (entries: MutableList<LogEntry>): void => {
// Direct iteration - no memory allocation for intermediate arrays
for (const entry of entries) {
if (entry.level === "ERROR") {
alertOnError(entry)
}
}
}
// Memory-efficient processing of large datasets
const analyzeMetrics = (metrics: MutableList<Metric>): MetricSummary => {
let sum = 0
let count = 0
let max = Number.MIN_SAFE_INTEGER
let min = Number.MAX_SAFE_INTEGER
// O(n) time, O(1) space - no array copies
for (const metric of metrics) {
sum += metric.value
count += 1
max = Math.max(max, metric.value)
min = Math.min(min, metric.value)
}
return {
average: sum / count,
total: sum,
count,
max,
min
}
}// Streaming data processor using MutableList
class StreamingDataProcessor<T> {
private buffer = MutableList.empty<T>()
private readonly bufferSize: number
constructor(bufferSize: number = 1000) {
this.bufferSize = bufferSize
}
addData(item: T): Effect.Effect<void> {
return Effect.gen(function* () {
MutableList.append(this.buffer, item)
// Process in chunks when buffer is full
if (MutableList.length(this.buffer) >= this.bufferSize) {
yield* this.processBufferChunk()
}
}.bind(this))
}
private processBufferChunk(): Effect.Effect<void> {
return Effect.gen(function* () {
const processed: T[] = []
// Efficient iteration without array conversion
for (const item of this.buffer) {
const processedItem = yield* this.processItem(item)
processed.push(processedItem)
}
// Send processed chunk
yield* this.sendProcessedChunk(processed)
// Clear buffer for next chunk
MutableList.reset(this.buffer)
}.bind(this))
}
private processItem(item: T): Effect.Effect<T> {
// Placeholder for item processing logic
return Effect.succeed(item)
}
private sendProcessedChunk(chunk: T[]): Effect.Effect<void> {
// Placeholder for sending processed data
return Effect.succeed(undefined)
}
}MutableList provides efficient memory management through strategic reset and reuse patterns:
import { MutableList, Effect } from "effect"
// Reusable buffer pool to minimize garbage collection
class MutableListPool<T> {
private pool = MutableList.empty<MutableList<T>>()
private readonly maxPoolSize: number
constructor(maxPoolSize: number = 10) {
this.maxPoolSize = maxPoolSize
}
// O(1) - Get or create a list
acquire(): MutableList<T> {
const pooled = MutableList.shift(this.pool)
return pooled || MutableList.empty<T>()
}
// O(1) - Return list to pool
release(list: MutableList<T>): void {
if (MutableList.length(this.pool) < this.maxPoolSize) {
MutableList.reset(list) // Clear contents but keep structure
MutableList.append(this.pool, list)
}
// If pool is full, let GC handle the list
}
// Create scoped list usage
withList<R>(operation: (list: MutableList<T>) => Effect.Effect<R>): Effect.Effect<R> {
return Effect.gen(function* () {
const list = this.acquire()
try {
const result = yield* operation(list)
return result
} finally {
this.release(list)
}
}.bind(this))
}
}
// Usage in high-throughput scenarios
const processHighVolumeData = Effect.gen(function* () {
const listPool = new MutableListPool<DataPoint>(5)
const processDataBatch = (batch: DataPoint[]) =>
listPool.withList(list => Effect.gen(function* () {
// Fill list with batch data
for (const dataPoint of batch) {
MutableList.append(list, dataPoint)
}
// Process efficiently
const results: ProcessedData[] = []
for (const point of list) {
const processed = yield* processDataPoint(point)
results.push(processed)
}
return results
// List automatically returned to pool when scope exits
}))
// Process multiple batches reusing pooled lists
for (let i = 0; i < 100; i++) {
const batch = yield* getBatchData(i)
const results = yield* processDataBatch(batch)
yield* saveResults(results)
}
})// Resource-aware list management
class ResourceManagedList<T> {
private list = MutableList.empty<T>()
private memoryUsage = 0
private readonly maxMemoryMB: number
constructor(maxMemoryMB: number = 100) {
this.maxMemoryMB = maxMemoryMB
}
addItem(item: T, estimatedSizeBytes: number): Effect.Effect<boolean> {
return Effect.gen(function* () {
const estimatedSizeMB = estimatedSizeBytes / (1024 * 1024)
if (this.memoryUsage + estimatedSizeMB > this.maxMemoryMB) {
// Try to free space by removing old items
const freed = yield* this.freeMemory(estimatedSizeMB)
if (!freed) {
return false // Cannot add item
}
}
MutableList.append(this.list, item)
this.memoryUsage += estimatedSizeMB
return true
}.bind(this))
}
private freeMemory(requiredMB: number): Effect.Effect<boolean> {
return Effect.gen(function* () {
let freed = 0
let removedCount = 0
// Remove from front (oldest items) until we have enough space
while (freed < requiredMB && !MutableList.isEmpty(this.list)) {
const removed = MutableList.shift(this.list)
if (removed) {
// Estimate freed memory (simplified)
freed += this.estimateItemSize(removed) / (1024 * 1024)
removedCount++
}
}
this.memoryUsage -= freed
if (removedCount > 0) {
console.log(`Freed ${freed.toFixed(2)}MB by removing ${removedCount} items`)
}
return freed >= requiredMB
}.bind(this))
}
private estimateItemSize(item: T): number {
// Simplified size estimation
return JSON.stringify(item).length * 2 // Rough estimate for JS object overhead
}
getMemoryInfo(): { usageMB: number; itemCount: number; maxMB: number } {
return {
usageMB: this.memoryUsage,
itemCount: MutableList.length(this.list),
maxMB: this.maxMemoryMB
}
}
}Leveraging MutableList's O(1) operations for maximum efficiency:
// Converting between data structures efficiently
class DataStructureConverter {
// Array to MutableList - O(n) but single pass
static arrayToMutableList<T>(array: readonly T[]): MutableList<T> {
return MutableList.fromIterable(array)
}
// MutableList to Array - O(n) using built-in iterator
static mutableListToArray<T>(list: MutableList<T>): T[] {
return Array.from(list)
}
// Merge multiple MutableLists efficiently
static mergeLists<T>(...lists: MutableList<T>[]): MutableList<T> {
const result = MutableList.empty<T>()
for (const list of lists) {
// O(n) for each list - append each element
MutableList.forEach(list, item => MutableList.append(result, item))
}
return result
}
// Split MutableList at position - O(n) but efficient
static splitAt<T>(list: MutableList<T>, position: number): [MutableList<T>, MutableList<T>] {
const left = MutableList.empty<T>()
const right = MutableList.empty<T>()
let index = 0
for (const item of list) {
if (index < position) {
MutableList.append(left, item)
} else {
MutableList.append(right, item)
}
index++
}
return [left, right]
}
// Partition based on predicate
static partition<T>(
list: MutableList<T>,
predicate: (item: T) => boolean
): [MutableList<T>, MutableList<T>] {
const truthyItems = MutableList.empty<T>()
const falsyItems = MutableList.empty<T>()
for (const item of list) {
if (predicate(item)) {
MutableList.append(truthyItems, item)
} else {
MutableList.append(falsyItems, item)
}
}
return [truthyItems, falsyItems]
}
}
// Performance-optimized batch operations
const performBatchOperations = Effect.gen(function* () {
const sourceData = Array.from({ length: 10000 }, (_, i) => ({ id: i, value: Math.random() }))
// Convert to MutableList once
const dataList = DataStructureConverter.arrayToMutableList(sourceData)
// Efficient filtering and partitioning
const [highValue, lowValue] = DataStructureConverter.partition(
dataList,
item => item.value > 0.5
)
console.log(`High value items: ${MutableList.length(highValue)}`)
console.log(`Low value items: ${MutableList.length(lowValue)}`)
// Process each partition efficiently
MutableList.forEach(highValue, item => {
// Process high-value items
processHighValueItem(item)
})
MutableList.forEach(lowValue, item => {
// Process low-value items
processLowValueItem(item)
})
})import { MutableList, Effect, Option } from "effect"
// Generic queue with O(1) operations
class EfficiientQueue<T> {
private items = MutableList.empty<T>()
// O(1) - Add to back of queue
enqueue(item: T): void {
MutableList.append(this.items, item)
}
// O(1) - Remove from front of queue
dequeue(): Option.Option<T> {
const item = MutableList.shift(this.items)
return item ? Option.some(item) : Option.none()
}
// O(1) - Peek at front without removing
peek(): Option.Option<T> {
return Option.fromNullable(MutableList.head(this.items))
}
// O(1) - Check if empty
isEmpty(): boolean {
return MutableList.isEmpty(this.items)
}
// O(1) - Get size
size(): number {
return MutableList.length(this.items)
}
// O(n) - Process all items without removing
forEach(callback: (item: T) => void): void {
MutableList.forEach(this.items, callback)
}
// O(1) - Clear all items
clear(): void {
MutableList.reset(this.items)
}
}
// Priority queue using multiple MutableLists
class PriorityQueue<T> {
private highPriority = MutableList.empty<T>()
private normalPriority = MutableList.empty<T>()
private lowPriority = MutableList.empty<T>()
enqueue(item: T, priority: "high" | "normal" | "low" = "normal"): void {
switch (priority) {
case "high":
MutableList.append(this.highPriority, item)
break
case "normal":
MutableList.append(this.normalPriority, item)
break
case "low":
MutableList.append(this.lowPriority, item)
break
}
}
dequeue(): Option.Option<T> {
// Process high priority first
let item = MutableList.shift(this.highPriority)
if (item) return Option.some(item)
// Then normal priority
item = MutableList.shift(this.normalPriority)
if (item) return Option.some(item)
// Finally low priority
item = MutableList.shift(this.lowPriority)
return item ? Option.some(item) : Option.none()
}
getStatus(): { high: number; normal: number; low: number; total: number } {
return {
high: MutableList.length(this.highPriority),
normal: MutableList.length(this.normalPriority),
low: MutableList.length(this.lowPriority),
total: MutableList.length(this.highPriority) +
MutableList.length(this.normalPriority) +
MutableList.length(this.lowPriority)
}
}
}// Stack implementation with O(1) operations
class EfficientStack<T> {
private items = MutableList.empty<T>()
// O(1) - Push to end (top of stack)
push(item: T): void {
MutableList.append(this.items, item)
}
// O(1) - Pop from end (top of stack)
pop(): Option.Option<T> {
const item = MutableList.pop(this.items)
return item ? Option.some(item) : Option.none()
}
// O(1) - Peek at top without removing
peek(): Option.Option<T> {
return Option.fromNullable(MutableList.tail(this.items))
}
isEmpty(): boolean {
return MutableList.isEmpty(this.items)
}
size(): number {
return MutableList.length(this.items)
}
clear(): void {
MutableList.reset(this.items)
}
}
// Call stack tracker for debugging
class CallStackTracker {
private stack = new EfficientStack<{ function: string; timestamp: Date; args: unknown[] }>()
enterFunction(functionName: string, ...args: unknown[]): Effect.Effect<void> {
return Effect.sync(() => {
this.stack.push({
function: functionName,
timestamp: new Date(),
args
})
})
}
exitFunction(): Effect.Effect<Option.Option<string>> {
return Effect.sync(() => {
const call = this.stack.pop()
return Option.map(call, c => c.function)
})
}
getCurrentCallStack(): Effect.Effect<string[]> {
return Effect.sync(() => {
const calls: string[] = []
this.stack.forEach(call => calls.unshift(call.function)) // Reverse order for stack trace
return calls
})
}
getStackDepth(): number {
return this.stack.size()
}
}// Fixed-size circular buffer using MutableList
class CircularBuffer<T> {
private items = MutableList.empty<T>()
private readonly maxSize: number
constructor(maxSize: number) {
this.maxSize = maxSize
}
// O(1) - Add item, removing oldest if at capacity
add(item: T): Option.Option<T> {
let removed: Option.Option<T> = Option.none()
if (MutableList.length(this.items) >= this.maxSize) {
const oldestItem = MutableList.shift(this.items)
removed = oldestItem ? Option.some(oldestItem) : Option.none()
}
MutableList.append(this.items, item)
return removed
}
// Get all items in order (oldest to newest)
getItems(): T[] {
return Array.from(this.items)
}
// Get latest N items
getLatest(count: number): T[] {
const allItems = Array.from(this.items)
return allItems.slice(-count)
}
// Get oldest N items
getOldest(count: number): T[] {
const result: T[] = []
let collected = 0
for (const item of this.items) {
if (collected >= count) break
result.push(item)
collected++
}
return result
}
isFull(): boolean {
return MutableList.length(this.items) >= this.maxSize
}
clear(): void {
MutableList.reset(this.items)
}
}
// Usage for metrics collection
const metricsCollector = Effect.gen(function* () {
const metricsBuffer = new CircularBuffer<{ timestamp: Date; value: number }>(1000)
// Continuously collect metrics
for (let i = 0; i < 1500; i++) {
const metric = { timestamp: new Date(), value: Math.random() * 100 }
const evicted = metricsBuffer.add(metric)
if (Option.isSome(evicted)) {
// Log evicted old metric if needed
console.log(`Evicted old metric from ${evicted.value.timestamp}`)
}
yield* Effect.sleep(10) // Simulate time passing
}
// Get latest 100 metrics for analysis
const recentMetrics = metricsBuffer.getLatest(100)
console.log(`Analyzed ${recentMetrics.length} recent metrics`)
})import { MutableList, Effect, Stream } from "effect"
// Buffer Stream chunks efficiently
const streamBufferExample = Effect.gen(function* () {
const buffer = MutableList.empty<number>()
// Create a stream that processes items in batches
const processedStream = Stream.fromIterable([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).pipe(
Stream.tap(item => Effect.sync(() => MutableList.append(buffer, item))),
Stream.groupedWithin(3, "1 second"), // Group into chunks
Stream.tap(chunk =>
Effect.sync(() => {
console.log(`Processing chunk of ${chunk.length} items`)
// Process the chunk using buffer contents
const bufferArray = Array.from(buffer)
console.log(`Buffer contains: ${bufferArray}`)
// Clear buffer after processing
MutableList.reset(buffer)
})
)
)
// Run the stream
yield* Stream.runDrain(processedStream)
})
// Stream to MutableList collector
const streamCollector = <T>(stream: Stream.Stream<T>): Effect.Effect<MutableList<T>> =>
Effect.gen(function* () {
const collected = MutableList.empty<T>()
yield* Stream.runForEach(stream, item =>
Effect.sync(() => MutableList.append(collected, item))
)
return collected
})import { MutableList, Effect, TestClock, TestContext } from "effect"
// Test helper for MutableList operations
const testMutableListOperations = Effect.gen(function* () {
const list = MutableList.empty<string>()
// Test append operations
MutableList.append(list, "first")
MutableList.append(list, "second")
MutableList.append(list, "third")
// Verify state
const length = MutableList.length(list)
const head = MutableList.head(list)
const tail = MutableList.tail(list)
console.assert(length === 3, "Length should be 3")
console.assert(head === "first", "Head should be 'first'")
console.assert(tail === "third", "Tail should be 'third'")
// Test pop operation
const popped = MutableList.pop(list)
console.assert(popped === "third", "Popped item should be 'third'")
console.assert(MutableList.length(list) === 2, "Length should be 2 after pop")
// Test shift operation
const shifted = MutableList.shift(list)
console.assert(shifted === "first", "Shifted item should be 'first'")
console.assert(MutableList.length(list) === 1, "Length should be 1 after shift")
console.log("All MutableList tests passed!")
})
// Property-based testing helper
const testMutableListProperties = Effect.gen(function* () {
const list = MutableList.empty<number>()
// Test that append then pop gives same item
const testItem = 42
MutableList.append(list, testItem)
const popped = MutableList.pop(list)
console.assert(popped === testItem, "Append then pop should return same item")
// Test that prepend then shift gives same item
MutableList.prepend(list, testItem)
const shifted = MutableList.shift(list)
console.assert(shifted === testItem, "Prepend then shift should return same item")
// Test length consistency
const initialLength = MutableList.length(list)
MutableList.append(list, 1)
MutableList.append(list, 2)
MutableList.append(list, 3)
console.assert(
MutableList.length(list) === initialLength + 3,
"Length should increase by number of appends"
)
console.log("All property tests passed!")
})// Serialization helpers for MutableList
class MutableListSerializer {
static toJSON<T>(list: MutableList<T>): T[] {
return Array.from(list)
}
static fromJSON<T>(data: T[]): MutableList<T> {
return MutableList.fromIterable(data)
}
static stringify<T>(list: MutableList<T>): string {
return JSON.stringify(this.toJSON(list))
}
static parse<T>(json: string): MutableList<T> {
const data = JSON.parse(json) as T[]
return this.fromJSON(data)
}
}
// Usage in data persistence
const dataPersistenceExample = Effect.gen(function* () {
const taskList = MutableList.empty<{ id: string; title: string; completed: boolean }>()
// Add some tasks
MutableList.append(taskList, { id: "1", title: "Learn Effect", completed: false })
MutableList.append(taskList, { id: "2", title: "Build app", completed: false })
MutableList.append(taskList, { id: "3", title: "Deploy", completed: false })
// Serialize to JSON
const serialized = MutableListSerializer.stringify(taskList)
console.log("Serialized:", serialized)
// Save to storage (simulated)
yield* Effect.succeed(localStorage.setItem("tasks", serialized))
// Load from storage
const loaded = yield* Effect.succeed(localStorage.getItem("tasks"))
if (loaded) {
const deserializedList = MutableListSerializer.parse(loaded)
console.log("Loaded tasks count:", MutableList.length(deserializedList))
// Verify data integrity
MutableList.forEach(deserializedList, task => {
console.log(`Task: ${task.title} (${task.completed ? 'done' : 'pending'})`)
})
}
})MutableList provides efficient O(1) insertion and deletion operations at both ends of a doubly-linked list, making it ideal for scenarios requiring frequent modifications without the overhead of array shifting.
Key benefits:
- Performance: O(1) operations for head/tail insertion, deletion, and access
- Memory Efficiency: No need to copy or shift elements during modifications
- Iterator Support: Seamless integration with JavaScript iteration protocols
- Mutation Optimization: Designed for scenarios where mutation is preferred over immutability
MutableList is particularly valuable for implementing queues, stacks, undo systems, streaming buffers, and any application requiring efficient insertion and removal operations at list boundaries.