UNPKG

@effect-ts/system

Version:

Effect-TS is a zero dependency set of libraries to write highly productive, purely functional TypeScript at scale.

1,931 lines (1,764 loc) 90.3 kB
// ets_tracing: off /* eslint-disable prefer-const */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable no-var */ import type { Either } from "../../../Either/index.js" import { identity } from "../../../Function/index.js" import * as O from "../../../Option/index.js" import type { Ord } from "../../../Ord/index.js" import * as St from "../../../Structural/index.js" import * as Tp from "../Tuple/index.js" /** * Forked from https://github.com/funkia/list/blob/master/src/index.ts * * All credits to original authors. * * The implementation has been forked to adapt to the double standard pipeable/data first * available in the remaining modules and to remove the fantasy-land bias. */ const branchingFactor = 32 const branchBits = 5 const mask = 31 function elementEquals(a: any, b: any): boolean { if (a === b) { return true } else { return false } } function createPath(depth: number, value: any): any { let current = value for (let i = 0; i < depth; ++i) { current = new Node(undefined, [current]) } return current } // Array helper functions function copyArray(source: any[]): any[] { const array = [] for (let i = 0; i < source.length; ++i) { array[i] = source[i] } return array } function pushElements<A>( source: A[], target: A[], offset: number, amount: number ): void { for (let i = offset; i < offset + amount; ++i) { target.push(source[i]!) } } function copyIndices( source: any[], sourceStart: number, target: any[], targetStart: number, length: number ): void { for (let i = 0; i < length; ++i) { target[targetStart + i] = source[sourceStart + i] } } function arrayPrepend<A>(value: A, array: A[]): A[] { const newLength = array.length + 1 const result = new Array(newLength) result[0] = value for (let i = 1; i < newLength; ++i) { result[i] = array[i - 1] } return result } /** * Create a reverse _copy_ of an array. */ function reverseArray<A>(array: A[]): A[] { return array.slice().reverse() } function arrayFirst<A>(array: A[]): A { return array[0]! } function arrayLast<A>(array: A[]): A { return array[array.length - 1]! } const pathResult = { path: 0, index: 0, updatedOffset: 0 } type PathResult = typeof pathResult function getPath( index: number, offset: number, depth: number, sizes: Sizes ): PathResult { if (sizes === undefined && offset !== 0) { pathResult.updatedOffset = 0 index = handleOffset(depth, offset, index) } let path = (index >> (depth * branchBits)) & mask if (sizes !== undefined) { while (sizes[path]! <= index) { path++ } const traversed = path === 0 ? 0 : sizes[path - 1]! index -= traversed pathResult.updatedOffset = offset } pathResult.path = path pathResult.index = index return pathResult } function updateNode( node: Node, depth: number, index: number, offset: number, value: any ): Node { const { index: newIndex, path, updatedOffset } = getPath(index, offset, depth, node.sizes) const array = copyArray(node.array) array[path] = depth > 0 ? updateNode(array[path], depth - 1, newIndex, updatedOffset, value) : value return new Node(node.sizes, array) } export type Sizes = number[] | undefined export class Node { constructor(public sizes: Sizes, public array: any[]) {} } function cloneNode({ array, sizes }: Node): Node { return new Node(sizes === undefined ? undefined : copyArray(sizes), copyArray(array)) } // This array should not be mutated. Thus a dummy element is placed in // it. Thus the affix will not be owned and thus not mutated. const emptyAffix: any[] = [0] // We store a bit field in list. From right to left, the first five // bits are suffix length, the next five are prefix length and the // rest is depth. The functions below are for working with the bits in // a sane way. const affixBits = 6 const affixMask = 0b111111 function getSuffixSize(l: List<any>): number { return l.bits & affixMask } function getPrefixSize(l: List<any>): number { return (l.bits >> affixBits) & affixMask } function getDepth(l: List<any>): number { return l.bits >> (affixBits * 2) } function setPrefix(size: number, bits: number): number { return (size << affixBits) | (bits & ~(affixMask << affixBits)) } function setSuffix(size: number, bits: number): number { return size | (bits & ~affixMask) } function setDepth(depth: number, bits: number): number { return (depth << (affixBits * 2)) | (bits & (affixMask | (affixMask << affixBits))) } function incrementPrefix(bits: number): number { return bits + (1 << affixBits) } function incrementSuffix(bits: number): number { return bits + 1 } function incrementDepth(bits: number): number { return bits + (1 << (affixBits * 2)) } function decrementDepth(bits: number): number { return bits - (1 << (affixBits * 2)) } /* * Invariants that any list `l` should satisfy * * 1. If `l.root !== undefined` then `getSuffixSize(l) !== 0` and * `getPrefixSize(l) !== 0`. The invariant ensures that `first` and * `last` never have to look in the root and that they therefore * take O(1) time. * 2. If a tree or sub-tree does not have a size-table then all leaf * nodes in the tree are of size 32. */ /** * Represents a list of elements. */ export class List<A> implements Iterable<A>, St.HasEquals, St.HasHash { constructor( readonly bits: number, readonly offset: number, readonly length: number, readonly prefix: A[], readonly root: Node | undefined, readonly suffix: A[] ) {} [Symbol.iterator](): Iterator<A> { return new ForwardListIterator(this) } toJSON(): readonly A[] { return toArray(this) } [St.equalsSym](that: unknown): boolean { return that instanceof List && equalsWith_(this, that, St.equals) } get [St.hashSym](): number { return St.hashIterator(this[Symbol.iterator]()) } } export type MutableList<A> = { -readonly [K in keyof List<A>]: List<A>[K] } & { [Symbol.iterator]: () => Iterator<A> // This property doesn't exist at run-time. It exists to prevent a // MutableList from being assignable to a List. "@@mutable": true } function cloneList<A>(l: List<A>): MutableList<A> { return new List(l.bits, l.offset, l.length, l.prefix, l.root, l.suffix) as any } abstract class ListIterator<A> implements Iterator<A> { stack: any[][] | undefined indices: number[] | undefined idx: number prefixSize: number middleSize: number result: IteratorResult<A> = { done: false, value: undefined as any } constructor(protected l: List<A>, direction: 1 | -1) { this.idx = direction === 1 ? -1 : l.length this.prefixSize = getPrefixSize(l) this.middleSize = l.length - getSuffixSize(l) if (l.root !== undefined) { const depth = getDepth(l) this.stack = new Array(depth + 1) this.indices = new Array(depth + 1) let currentNode = l.root.array for (let i = depth; 0 <= i; --i) { this.stack[i] = currentNode const idx = direction === 1 ? 0 : currentNode.length - 1 this.indices[i] = idx currentNode = currentNode[idx].array } this.indices[0] -= direction } } abstract next(): IteratorResult<A> } class ForwardListIterator<A> extends ListIterator<A> { constructor(l: List<A>) { super(l, 1) } nextInTree(): void { for (var i = 0; ++this.indices![i] === this.stack![i]!.length; ++i) { this.indices![i] = 0 } for (; 0 < i; --i) { this.stack![i - 1] = this.stack![i]![this.indices![i]!].array } } next(): IteratorResult<A> { let newVal const idx = ++this.idx if (idx < this.prefixSize) { newVal = this.l.prefix[this.prefixSize - idx - 1] } else if (idx < this.middleSize) { this.nextInTree() newVal = this.stack![0]![this.indices![0]!] } else if (idx < this.l.length) { newVal = this.l.suffix[idx - this.middleSize] } else { this.result.done = true } this.result.value = newVal return this.result } } class BackwardsListIterator<A> extends ListIterator<A> { constructor(l: List<A>) { super(l, -1) } prevInTree(): void { for (var i = 0; this.indices![i] === 0; ++i) { // } --this.indices![i] for (; 0 < i; --i) { const n = this.stack![i]![this.indices![i]!].array this.stack![i - 1] = n this.indices![i - 1] = n.length - 1 } } next(): IteratorResult<A> { let newVal const idx = --this.idx if (this.middleSize <= idx) { newVal = this.l.suffix[idx - this.middleSize] } else if (this.prefixSize <= idx) { this.prevInTree() newVal = this.stack![0]![this.indices![0]!] } else if (0 <= idx) { newVal = this.l.prefix[this.prefixSize - idx - 1] } else { this.result.done = true } this.result.value = newVal return this.result } } /** * Returns an iterable that iterates backwards over the given list. * * @complexity O(1) */ export function backwards<A>(l: List<A>): Iterable<A> { return { [Symbol.iterator](): Iterator<A> { return new BackwardsListIterator(l) } } } export function emptyPushable<A>(): MutableList<A> { return new List(0, 0, 0, [], undefined, []) as any } /** Appends the value to the list by _mutating_ the list and its content. */ export function push_<A>(l: MutableList<A>, value: A): MutableList<A> { const suffixSize = getSuffixSize(l) if (l.length === 0) { l.bits = setPrefix(1, l.bits) l.prefix = [value] } else if (suffixSize < 32) { l.bits = incrementSuffix(l.bits) l.suffix.push(value) } else if (l.root === undefined) { l.root = new Node(undefined, l.suffix) l.suffix = [value] l.bits = setSuffix(1, l.bits) } else { const newNode = new Node(undefined, l.suffix) const index = l.length - 1 - 32 + 1 let current = l.root! let depth = getDepth(l) l.suffix = [value] l.bits = setSuffix(1, l.bits) if (index - 1 < branchingFactor ** (depth + 1)) { for (; depth >= 0; --depth) { const path = (index >> (depth * branchBits)) & mask if (path < current.array.length) { current = current.array[path] } else { current.array.push(createPath(depth - 1, newNode)) break } } } else { l.bits = incrementDepth(l.bits) l.root = new Node(undefined, [l.root, createPath(depth, newNode)]) } } l.length++ return l } /** * Creates a list of the given elements. * * @complexity O(n) */ export function list<A>(...elements: A[]): List<A> { const l = emptyPushable<A>() for (const element of elements) { push_(l, element) } return l } /** * Creates an empty list. * * @complexity O(1) */ export function empty<A = any>(): List<A> { return new List(0, 0, 0, emptyAffix, undefined, emptyAffix) } /** * Takes a single arguments and returns a singleton list that contains it. * * @complexity O(1) */ export function of<A>(a: A): List<A> { return list(a) } /** * Takes two arguments and returns a list that contains them. * * @complexity O(1) */ export function pair<A>(second: A): (first: A) => List<A> { return (first: A) => pair_(first, second) } /** * Takes two arguments and returns a list that contains them. * * @complexity O(1) */ export function pair_<A>(first: A, second: A): List<A> { return new List(2, 0, 2, emptyAffix, undefined, [first, second]) } /** * Converts an array, an array-like, or an iterable into a list. * * @complexity O(n) */ export function from<A>(sequence: A[] | ArrayLike<A> | Iterable<A>): List<A> export function from<A>(sequence: any): List<A> { const l = emptyPushable<A>() if (sequence.length > 0 && (sequence[0] !== undefined || 0 in sequence)) { for (let i = 0; i < sequence.length; ++i) { push_(l, sequence[i]) } } else if (Symbol.iterator in sequence) { const iterator = sequence[Symbol.iterator]() let cur // tslint:disable-next-line:no-conditional-assignment while (!(cur = iterator.next()).done) { push_(l, cur.value) } } return l } /** * Returns a list of numbers between an inclusive lower bound and an exclusive upper bound. * * @complexity O(n) */ export function range(end: number): (start: number) => List<number> { return (start) => range_(start, end) } /** * Returns a list of numbers between an inclusive lower bound and an exclusive upper bound. * * @complexity O(n) */ export function range_(start: number, end: number): List<number> { const list = emptyPushable<number>() for (let i = start; i < end; ++i) { push_(list, i) } return list } /** * Returns a list of a given length that contains the specified value * in all positions. * * @complexity O(n) */ export function repeat(times: number): <A>(value: A) => List<A> { return (value) => repeat_(value, times) } /** * Returns a list of a given length that contains the specified value * in all positions. * * @complexity O(n) */ export function repeat_<A>(value: A, times: number): List<A> { const l = emptyPushable<A>() while (--times >= 0) { push_(l, value) } return l } /** * Generates a new list by calling a function with the current index * `n` times. * * @complexity O(n) */ export function times(times: number): <A>(func: (index: number) => A) => List<A> { return (func) => times_(func, times) } /** * Generates a new list by calling a function with the current index * `n` times. * * @complexity O(n) */ export function times_<A>(func: (index: number) => A, times: number): List<A> { const l = emptyPushable<A>() for (let i = 0; i < times; i++) { push_(l, func(i)) } return l } function nodeNthDense(node: Node, depth: number, index: number): any { let current = node for (; depth >= 0; --depth) { current = current.array[(index >> (depth * branchBits)) & mask] } return current } function handleOffset(depth: number, offset: number, index: number): number { index += offset for (; depth >= 0; --depth) { index = index - (offset & (mask << (depth * branchBits))) if (((index >> (depth * branchBits)) & mask) !== 0) { break } } return index } function nodeNth(node: Node, depth: number, offset: number, index: number): any { let path let current = node while (current.sizes !== undefined) { path = (index >> (depth * branchBits)) & mask while (current.sizes[path]! <= index) { path++ } if (path !== 0) { index -= current.sizes[path - 1]! offset = 0 // Offset is discarded if the left spine isn't traversed } depth-- current = current.array[path] } return nodeNthDense( current, depth, offset === 0 ? index : handleOffset(depth, offset, index) ) } /** * Gets the nth element of the list. If `n` is out of bounds * `undefined` is returned. * * @complexity O(log(n)) */ export function unsafeNth_<A>(l: List<A>, index: number): A | undefined { return O.toUndefined(nth_(l, index)) } /** * Gets the nth element of the list. If `n` is out of bounds * `undefined` is returned. * * @complexity O(log(n)) */ export function unsafeNth(index: number): <A>(l: List<A>) => A | undefined { return (l) => unsafeNth_(l, index) } /** * Gets the nth element of the list. If `n` is out of bounds * `undefined` is returned. * * @complexity O(log(n)) */ export function nth_<A>(l: List<A>, index: number): O.Option<A> { if (index < 0 || l.length <= index) { return O.none } const prefixSize = getPrefixSize(l) const suffixSize = getSuffixSize(l) if (index < prefixSize) { return O.some(l.prefix[prefixSize - index - 1]!) } else if (index >= l.length - suffixSize) { return O.some(l.suffix[index - (l.length - suffixSize)]!) } const { offset } = l const depth = getDepth(l) return O.some( l.root!.sizes === undefined ? nodeNthDense( l.root!, depth, offset === 0 ? index - prefixSize : handleOffset(depth, offset, index - prefixSize) ) : nodeNth(l.root!, depth, offset, index - prefixSize) ) } /** * Gets the nth element of the list. If `n` is out of bounds * `undefined` is returned. * * @complexity O(log(n)) */ export function nth(index: number): <A>(l: List<A>) => O.Option<A> { return (l) => nth_(l, index) } function setSizes(node: Node, height: number): Node { let sum = 0 const sizeTable = [] for (let i = 0; i < node.array.length; ++i) { sum += sizeOfSubtree(node.array[i], height - 1) sizeTable[i] = sum } node.sizes = sizeTable return node } /** * Returns the number of elements stored in the node. */ function sizeOfSubtree(node: Node, height: number): number { if (height !== 0) { if (node.sizes !== undefined) { return arrayLast(node.sizes) } else { // the node is leftwise dense so all all but the last child are full const lastSize = sizeOfSubtree(arrayLast(node.array), height - 1) return ((node.array.length - 1) << (height * branchBits)) + lastSize } } else { return node.array.length } } // prepend & append function affixPush<A>(a: A, array: A[], length: number): A[] { if (array.length === length) { array.push(a) return array } else { const newArray: A[] = [] copyIndices(array, 0, newArray, 0, length) newArray.push(a) return newArray } } /** * Prepends an element to the front of a list and returns the new list. * * @complexity O(1) */ export function prepend_<A>(l: List<A>, value: A): List<A> { const prefixSize = getPrefixSize(l) if (prefixSize < 32) { return new List<A>( incrementPrefix(l.bits), l.offset, l.length + 1, affixPush(value, l.prefix, prefixSize), l.root, l.suffix ) } else { const newList = cloneList(l) prependNodeToTree(newList, reverseArray(l.prefix)) const newPrefix = [value] newList.prefix = newPrefix newList.length++ newList.bits = setPrefix(1, newList.bits) return newList } } /** * Prepends an element to the front of a list and returns the new list. * * @complexity O(1) */ export function prepend<A>(value: A): (l: List<A>) => List<A> { return (l) => prepend_(l, value) } /** * Traverses down the left edge of the tree and copies k nodes. * Returns the last copied node. * @param l * @param k The number of nodes to copy. Should always be at least 1. */ function copyLeft(l: MutableList<any>, k: number): Node { let currentNode = cloneNode(l.root!) // copy root l.root = currentNode // install copy of root for (let i = 1; i < k; ++i) { const index = 0 // go left if (currentNode.sizes !== undefined) { for (let i = 0; i < currentNode.sizes.length; ++i) { currentNode.sizes[i] += 32 } } const newNode = cloneNode(currentNode.array[index]) // Install the copied node currentNode.array[index] = newNode currentNode = newNode } return currentNode } /** * Prepends an element to a node */ function nodePrepend(value: any, size: number, node: Node): Node { const array = arrayPrepend(value, node.array) let sizes = undefined if (node.sizes !== undefined) { sizes = new Array(node.sizes.length + 1) sizes[0] = size for (let i = 0; i < node.sizes.length; ++i) { sizes[i + 1] = node.sizes[i]! + size } } return new Node(sizes, array) } /** * Prepends a node to a tree. Either by shifting the nodes in the root * left or by increasing the height */ function prependTopTree<A>(l: MutableList<A>, depth: number, node: Node): number { let newOffset if (l.root!.array.length < branchingFactor) { // There is space in the root, there is never a size table in this // case newOffset = 32 ** depth - 32 l.root = new Node( undefined, arrayPrepend(createPath(depth - 1, node), l.root!.array) ) } else { // We need to create a new root l.bits = incrementDepth(l.bits) const sizes = l.root!.sizes === undefined ? undefined : [32, arrayLast(l.root!.sizes!) + 32] newOffset = depth === 0 ? 0 : 32 ** (depth + 1) - 32 l.root = new Node(sizes, [createPath(depth, node), l.root]) } return newOffset } /** * Takes a list and a node tail. It then prepends the node to the tree * of the list. * @param l The subject for prepending. `l` will be mutated. Nodes in * the tree will _not_ be mutated. * @param node The node that should be prepended to the tree. */ function prependNodeToTree<A>(l: MutableList<A>, array: A[]): List<A> { if (l.root === undefined) { if (getSuffixSize(l) === 0) { // ensure invariant 1 l.bits = setSuffix(array.length, l.bits) l.suffix = array } else { l.root = new Node(undefined, array) } return l } else { const node = new Node(undefined, array) const depth = getDepth(l) let newOffset = 0 if (l.root.sizes === undefined) { if (l.offset !== 0) { newOffset = l.offset - branchingFactor l.root = prependDense(l.root, depth, l.offset, node) } else { // in this case we can be sure that the is not room in the tree // for the new node newOffset = prependTopTree(l, depth, node) } } else { // represents how many nodes _with size-tables_ that we should copy. let copyableCount = 0 // go down while there is size tables let nodesTraversed = 0 let currentNode = l.root while (currentNode.sizes !== undefined && nodesTraversed < depth) { ++nodesTraversed if (currentNode.array.length < 32) { // there is room if offset is > 0 or if the first node does not // contain as many nodes as it possibly can copyableCount = nodesTraversed } currentNode = currentNode.array[0] } if (l.offset !== 0) { const copiedNode = copyLeft(l, nodesTraversed) for (let i = 0; i < copiedNode.sizes!.length; ++i) { copiedNode.sizes![i] += branchingFactor } copiedNode.array[0] = prependDense( copiedNode.array[0], depth - nodesTraversed, l.offset, node ) l.offset = l.offset - branchingFactor return l } else { if (copyableCount === 0) { l.offset = prependTopTree(l, depth, node) } else { let parent: Node | undefined let prependableNode: Node // Copy the part of the path with size tables if (copyableCount > 1) { parent = copyLeft(l, copyableCount - 1) prependableNode = parent.array[0] } else { parent = undefined prependableNode = l.root! } const path = createPath(depth - copyableCount, node) // add offset l.offset = 32 ** (depth - copyableCount + 1) - 32 const prepended = nodePrepend(path, 32, prependableNode) if (parent === undefined) { l.root = prepended } else { parent.array[0] = prepended } } return l } } l.offset = newOffset return l } } /** * Prepends a node to a dense tree. The given `offset` is never zero. */ function prependDense(node: Node, depth: number, offset: number, value: Node): Node { // We're indexing down `offset - 1`. At each step `path` is either 0 or -1. const curOffset = (offset >> (depth * branchBits)) & mask const path = (((offset - 1) >> (depth * branchBits)) & mask) - curOffset if (path < 0) { return new Node(undefined, arrayPrepend(createPath(depth - 1, value), node.array)) } else { const array = copyArray(node.array) array[0] = prependDense(array[0], depth - 1, offset, value) return new Node(undefined, array) } } /** * Appends an element to the end of a list and returns the new list. * * @complexity O(n) */ export function append_<A>(l: List<A>, value: A): List<A> { const suffixSize = getSuffixSize(l) if (suffixSize < 32) { return new List( incrementSuffix(l.bits), l.offset, l.length + 1, l.prefix, l.root, affixPush(value, l.suffix, suffixSize) ) } const newSuffix = [value] const newList = cloneList(l) appendNodeToTree(newList, l.suffix) newList.suffix = newSuffix newList.length++ newList.bits = setSuffix(1, newList.bits) return newList } /** * Appends an element to the end of a list and returns the new list. * * @complexity O(n) */ export function append<A>(value: A): (l: List<A>) => List<A> { return (l) => append_(l, value) } /** * Gets the length of a list. * * @complexity `O(1)` */ export function size(l: List<any>): number { return l.length } /** * Returns the first element of the list. If the list is empty the * function returns undefined. * * @complexity O(1) */ export function unsafeFirst<A>(l: List<A>): A | undefined { return O.toUndefined(first(l)) } /** * Returns the first element of the list. If the list is empty the * function returns undefined. * * @complexity O(1) */ export function first<A>(l: List<A>): O.Option<A> { const prefixSize = getPrefixSize(l) return prefixSize !== 0 ? O.some(l.prefix[prefixSize - 1]!) : l.length !== 0 ? O.some(l.suffix[0]!) : O.none } /** * Returns the last element of the list. If the list is empty the * function returns `undefined`. * * @complexity O(1) */ export function unsafeLast<A>(l: List<A>): A | undefined { return O.toUndefined(last(l)) } /** * Returns the last element of the list. If the list is empty the * function returns `undefined`. * * @complexity O(1) */ export function last<A>(l: List<A>): O.Option<A> { const suffixSize = getSuffixSize(l) return suffixSize !== 0 ? O.some(l.suffix[suffixSize - 1]!) : l.length !== 0 ? O.some(l.prefix[0]!) : O.none } // map function mapArray<A, B>(f: (a: A) => B, array: A[]): B[] { const result = new Array(array.length) for (let i = 0; i < array.length; ++i) { result[i] = f(array[i]!) } return result } function mapNode<A, B>(f: (a: A) => B, node: Node, depth: number): Node { if (depth !== 0) { const { array } = node const result = new Array(array.length) for (let i = 0; i < array.length; ++i) { result[i] = mapNode(f, array[i], depth - 1) } return new Node(node.sizes, result) } else { return new Node(undefined, mapArray(f, node.array)) } } function mapPrefix<A, B>(f: (a: A) => B, prefix: A[], length: number): B[] { const newPrefix = new Array(length) for (let i = length - 1; 0 <= i; --i) { newPrefix[i] = f(prefix[i]!) } return newPrefix } function mapAffix<A, B>(f: (a: A) => B, suffix: A[], length: number): B[] { const newSuffix = new Array(length) for (let i = 0; i < length; ++i) { newSuffix[i] = f(suffix[i]!) } return newSuffix } /** * Applies a function to each element in the given list and returns a * new list of the values that the function return. * * @complexity O(n) */ export function map_<A, B>(l: List<A>, f: (a: A) => B): List<B> { return new List( l.bits, l.offset, l.length, mapPrefix(f, l.prefix, getPrefixSize(l)), l.root === undefined ? undefined : mapNode(f, l.root, getDepth(l)), mapAffix(f, l.suffix, getSuffixSize(l)) ) } /** * Applies a function to each element in the given list and returns a * new list of the values that the function return. * * @complexity O(n) */ export function map<A, B>(f: (a: A) => B): (l: List<A>) => List<B> { return (l) => new List( l.bits, l.offset, l.length, mapPrefix(f, l.prefix, getPrefixSize(l)), l.root === undefined ? undefined : mapNode(f, l.root, getDepth(l)), mapAffix(f, l.suffix, getSuffixSize(l)) ) } /** * Extracts the specified property from each object in the list. */ export function pluck_<A, K extends keyof A>(l: List<A>, key: K): List<A[K]> { return map_(l, (a) => a[key]) } /** * Extracts the specified property from each object in the list. */ export function pluck<A, K extends keyof A>(key: K): (l: List<A>) => List<A[K]> { return (l) => pluck_(l, key) } // fold function foldlSuffix<A, B>( f: (acc: B, value: A) => B, acc: B, array: A[], length: number ): B { for (let i = 0; i < length; ++i) { acc = f(acc, array[i]!) } return acc } function foldlPrefix<A, B>( f: (acc: B, value: A) => B, acc: B, array: A[], length: number ): B { for (let i = length - 1; 0 <= i; --i) { acc = f(acc, array[i]!) } return acc } function foldlNode<A, B>( f: (acc: B, value: A) => B, acc: B, node: Node, depth: number ): B { const { array } = node if (depth === 0) { return foldlSuffix(f, acc, array, array.length) } for (let i = 0; i < array.length; ++i) { acc = foldlNode(f, acc, array[i], depth - 1) } return acc } /** * Folds a function over a list. Left-associative. */ export function reduce_<A, B>(l: List<A>, initial: B, f: (acc: B, value: A) => B): B { const suffixSize = getSuffixSize(l) const prefixSize = getPrefixSize(l) initial = foldlPrefix(f, initial, l.prefix, prefixSize) if (l.root !== undefined) { initial = foldlNode(f, initial, l.root, getDepth(l)) } return foldlSuffix(f, initial, l.suffix, suffixSize) } /** * Folds a function over a list. Left-associative. */ export function reduce<A, B>( initial: B, f: (acc: B, value: A) => B ): (l: List<A>) => B { return (l) => reduce_(l, initial, f) } /** * Folds a function over a list from left to right while collecting * all the intermediate steps in a resulting list. */ export function scan_<A, B>( l: List<A>, initial: B, f: (acc: B, value: A) => B ): List<B> { return reduce_(l, push_(emptyPushable<B>(), initial), (l2, a) => push_(l2, f(unsafeLast(l2)!, a)) ) } /** * Folds a function over a list from left to right while collecting * all the intermediate steps in a resulting list. */ export function scan<A, B>( initial: B, f: (acc: B, value: A) => B ): (l: List<A>) => List<B> { return (l) => scan_(l, initial, f) } /** * Invokes a given callback for each element in the list from left to * right. Returns `undefined`. * * This function is very similar to map. It should be used instead of * `map` when the mapping function has side-effects. Whereas `map` * constructs a new list `forEach` merely returns `undefined`. This * makes `forEach` faster when the new list is unneeded. * * @complexity O(n) */ export function forEach_<A>(l: List<A>, callback: (a: A) => void): void { reduce_(l, undefined as void, (_, element) => callback(element)) } /** * Invokes a given callback for each element in the list from left to * right. Returns `undefined`. * * This function is very similar to map. It should be used instead of * `map` when the mapping function has side-effects. Whereas `map` * constructs a new list `forEach` merely returns `undefined`. This * makes `forEach` faster when the new list is unneeded. * * @complexity O(n) */ export function forEach<A>(callback: (a: A) => void): (l: List<A>) => void { return (l) => forEach_(l, callback) } /** * Returns a new list that only contains the elements of the original * list for which the predicate returns `true`. * * @complexity O(n) */ export function filter_<A, B extends A>( l: List<A>, predicate: (a: A) => a is B ): List<B> export function filter_<A>(l: List<A>, predicate: (a: A) => boolean): List<A> export function filter_<A>(l: List<A>, predicate: (a: A) => boolean): List<A> { return reduce_(l, emptyPushable(), (acc, a) => (predicate(a) ? push_(acc, a) : acc)) } /** * Returns a new list that only contains the elements of the original * list for which the predicate returns `true`. * * @complexity O(n) */ export function filter<A, B extends A>( predicate: (a: A) => a is B ): (l: List<A>) => List<B> export function filter<A>(predicate: (a: A) => boolean): (l: List<A>) => List<A> export function filter<A>(predicate: (a: A) => boolean): (l: List<A>) => List<A> { return (l) => reduce_(l, emptyPushable(), (acc, a) => (predicate(a) ? push_(acc, a) : acc)) } /** * Returns a new list that only contains the elements of the original * list for which the f returns `Some`. * * @complexity O(n) */ export function filterMap_<A, B>(l: List<A>, f: (a: A) => O.Option<B>): List<B> { return reduce_(l, emptyPushable(), (acc, a) => { const fa = f(a) if (fa._tag === "Some") { push_(acc, fa.value) } return acc }) } /** * Returns a new list that only contains the elements of the original * list for which the f returns `Some`. * * @complexity O(n) */ export function filterMap<A, B>(f: (a: A) => O.Option<B>): (l: List<A>) => List<B> { return (l) => filterMap_(l, f) } /** * Filter out optional values */ export function compact<A>(fa: List<O.Option<A>>): List<A> { return filterMap((x: O.Option<A>) => x)(fa) } /** * Returns a new list that only contains the elements of the original * list for which the predicate returns `false`. * * @complexity O(n) */ export function filterNot_<A>(l: List<A>, predicate: (a: A) => boolean): List<A> { return reduce_(l, emptyPushable(), (acc, a) => (predicate(a) ? acc : push_(acc, a))) } /** * Returns a new list that only contains the elements of the original * list for which the predicate returns `false`. * * @complexity O(n) */ export function filterNot<A>(predicate: (a: A) => boolean): (l: List<A>) => List<A> { return (l) => filterNot_(l, predicate) } /** * Splits the list into two lists. One list that contains all the * values for which the predicate returns `true` and one containing * the values for which it returns `false`. * * @complexity O(n) */ export function partition_<A, B extends A>( l: List<A>, predicate: (a: A) => a is B ): Tp.Tuple<[List<B>, List<Exclude<A, B>>]> export function partition_<A>( l: List<A>, predicate: (a: A) => boolean ): Tp.Tuple<[List<A>, List<A>]> export function partition_<A>( l: List<A>, predicate: (a: A) => boolean ): Tp.Tuple<[List<A>, List<A>]> { return reduce_( l, Tp.tuple(emptyPushable<A>(), emptyPushable<A>()) as Tp.Tuple< [MutableList<A>, MutableList<A>] >, (arr, a) => (predicate(a) ? push_(arr.get(0), a) : push_(arr.get(1), a), arr) ) } /** * Splits the list into two lists. One list that contains all the * values for which the predicate returns `true` and one containing * the values for which it returns `false`. * * @complexity O(n) */ export function partition<A, B extends A>( predicate: (a: A) => a is B ): (l: List<A>) => Tp.Tuple<[List<B>, List<Exclude<A, B>>]> export function partition<A>( predicate: (a: A) => boolean ): (l: List<A>) => Tp.Tuple<[List<A>, List<A>]> export function partition<A>( predicate: (a: A) => boolean ): (l: List<A>) => Tp.Tuple<[List<A>, List<A>]> { return (l) => partition_(l, predicate) } /** * Splits the list into two lists. One list that contains the lefts * and one contains the rights * * @complexity O(n) */ export function partitionMap_<A, B, C>( l: List<A>, f: (_: A) => Either<B, C> ): Tp.Tuple<[List<B>, List<C>]> { return reduce_( l, Tp.tuple(emptyPushable<B>(), emptyPushable<C>()) as Tp.Tuple< [MutableList<B>, MutableList<C>] >, (arr, a) => { const fa = f(a) if (fa._tag === "Left") { push_(arr.get(0), fa.left) } else { push_(arr.get(1), fa.right) } return arr } ) } /** * Splits the list into two lists. One list that contains the lefts * and one contains the rights * * @complexity O(n) */ export function partitionMap<A, B, C>( f: (_: A) => Either<B, C> ): (l: List<A>) => Tp.Tuple<[List<B>, List<C>]> { return (l) => partitionMap_(l, f) } /** * Splits the list into two lists. One list that contains the lefts * and one contains the rights * * @complexity O(n) */ export function separate<B, C>(l: List<Either<B, C>>): Tp.Tuple<[List<B>, List<C>]> { return partitionMap_(l, identity) } /** * Concats the strings in the list separated by a specified separator. */ export function join_(l: List<string>, separator: string): string { return reduce_(l, "", (a, b) => (a.length === 0 ? b : a + separator + b)) } /** * Concats the strings in the list separated by a specified separator. */ export function join(separator: string): (l: List<string>) => string { return (l) => join_(l, separator) } function foldrSuffix<A, B>( f: (value: A, acc: B) => B, initial: B, array: A[], length: number ): B { let acc = initial for (let i = length - 1; 0 <= i; --i) { acc = f(array[i]!, acc) } return acc } function foldrPrefix<A, B>( f: (value: A, acc: B) => B, initial: B, array: A[], length: number ): B { let acc = initial for (let i = 0; i < length; ++i) { acc = f(array[i]!, acc) } return acc } function foldrNode<A, B>( f: (value: A, acc: B) => B, initial: B, { array }: Node, depth: number ): B { if (depth === 0) { return foldrSuffix(f, initial, array, array.length) } let acc = initial for (let i = array.length - 1; 0 <= i; --i) { acc = foldrNode(f, acc, array[i], depth - 1) } return acc } /** * Folds a function over a list. Right-associative. * * @complexity O(n) */ export function reduceRight_<A, B>( l: List<A>, initial: B, f: (value: A, acc: B) => B ): B { const suffixSize = getSuffixSize(l) const prefixSize = getPrefixSize(l) let acc = foldrSuffix(f, initial, l.suffix, suffixSize) if (l.root !== undefined) { acc = foldrNode(f, acc, l.root, getDepth(l)) } return foldrPrefix(f, acc, l.prefix, prefixSize) } /** * Folds a function over a list. Right-associative. * * @complexity O(n) */ export function reduceRight<A, B>( initial: B, f: (value: A, acc: B) => B ): (l: List<A>) => B { return (l) => reduceRight_(l, initial, f) } /** * Applies a list of functions to a list of values. */ export function ap_<A, B>(listF: List<(a: A) => B>, l: List<A>): List<B> { return flatten(map_(listF, (f) => map_(l, f))) } /** * Applies a list of functions to a list of values. */ export function ap<A, B>(l: List<A>): (listF: List<(a: A) => B>) => List<B> { return (listF) => ap_(listF, l) } /** * Flattens a list of lists into a list. Note that this function does * not flatten recursively. It removes one level of nesting only. * * @complexity O(n * log(m)), where n is the length of the outer list and m the length of the inner lists. */ export function flatten<A>(nested: List<List<A>>): List<A> { return reduce_<List<A>, List<A>>(nested, empty(), concat_) } /** * Maps a function over a list and concatenates all the resulting * lists together. */ export function chain_<A, B>(l: List<A>, f: (a: A) => List<B>): List<B> { return flatten(map_(l, f)) } /** * Maps a function over a list and concatenates all the resulting * lists together. */ export function chain<A, B>(f: (a: A) => List<B>): (l: List<A>) => List<B> { return (l) => chain_(l, f) } // callback fold type FoldCb<Input, State> = (input: Input, state: State) => boolean function foldlArrayCb<A, B>( cb: FoldCb<A, B>, state: B, array: A[], from: number, to: number ): boolean { for (var i = from; i < to && cb(array[i]!, state); ++i) { // } return i === to } function foldrArrayCb<A, B>( cb: FoldCb<A, B>, state: B, array: A[], from: number, to: number ): boolean { for (var i = from - 1; to <= i && cb(array[i]!, state); --i) { // } return i === to - 1 } function foldlNodeCb<A, B>( cb: FoldCb<A, B>, state: B, node: Node, depth: number ): boolean { const { array } = node if (depth === 0) { return foldlArrayCb(cb, state, array, 0, array.length) } const to = array.length for (let i = 0; i < to; ++i) { if (!foldlNodeCb(cb, state, array[i], depth - 1)) { return false } } return true } /** * This function is a lot like a fold. But the reducer function is * supposed to mutate its state instead of returning it. Instead of * returning a new state it returns a boolean that tells wether or not * to continue the fold. `true` indicates that the folding should * continue. */ function foldlCb<A, B>(cb: FoldCb<A, B>, state: B, l: List<A>): B { const prefixSize = getPrefixSize(l) if ( !foldrArrayCb(cb, state, l.prefix, prefixSize, 0) || (l.root !== undefined && !foldlNodeCb(cb, state, l.root, getDepth(l))) ) { return state } const suffixSize = getSuffixSize(l) foldlArrayCb(cb, state, l.suffix, 0, suffixSize) return state } function foldrNodeCb<A, B>( cb: FoldCb<A, B>, state: B, node: Node, depth: number ): boolean { const { array } = node if (depth === 0) { return foldrArrayCb(cb, state, array, array.length, 0) } for (let i = array.length - 1; 0 <= i; --i) { if (!foldrNodeCb(cb, state, array[i], depth - 1)) { return false } } return true } function foldrCb<A, B>(cb: FoldCb<A, B>, state: B, l: List<A>): B { const suffixSize = getSuffixSize(l) const prefixSize = getPrefixSize(l) if ( !foldrArrayCb(cb, state, l.suffix, suffixSize, 0) || (l.root !== undefined && !foldrNodeCb(cb, state, l.root, getDepth(l))) ) { return state } const prefix = l.prefix foldlArrayCb(cb, state, l.prefix, prefix.length - prefixSize, prefix.length) return state } // functions based on foldlCb type FoldlWhileState<A, B> = { predicate: (b: B, a: A) => boolean result: B f: (acc: B, value: A) => B } /** * Similar to `foldl`. But, for each element it calls the predicate function * _before_ the folding function and stops folding if it returns `false`. * * @category Folds * @example * const isOdd = (_acc:, x) => x % 2 === 1; * * const xs = L.list(1, 3, 5, 60, 777, 800); * foldlWhile(isOdd, (n, m) => n + m, 0, xs) //=> 9 * * const ys = L.list(2, 4, 6); * foldlWhile(isOdd, (n, m) => n + m, 111, ys) //=> 111 */ function foldlWhileCb<A, B>(a: A, state: FoldlWhileState<A, B>): boolean { if (state.predicate(state.result, a) === false) { return false } state.result = state.f(state.result, a) return true } export function reduceWhile_<A, B>( l: List<A>, initial: B, predicate: (acc: B, value: A) => boolean, f: (acc: B, value: A) => B ): B { return foldlCb<A, FoldlWhileState<A, B>>( foldlWhileCb, { predicate, f, result: initial }, l ).result } export function reduceWhile<A, B>( initial: B, predicate: (acc: B, value: A) => boolean, f: (acc: B, value: A) => B ): (l: List<A>) => B { return (l) => reduceWhile_(l, initial, predicate, f) } type PredState = { predicate: (a: any) => boolean result: any } function everyCb<A>(value: A, state: any): boolean { return (state.result = state.predicate(value)) } /** * Returns `true` if and only if the predicate function returns `true` * for all elements in the given list. * * @complexity O(n) */ export function every_<A>(l: List<A>, predicate: (a: A) => boolean): boolean { return foldlCb<A, PredState>(everyCb, { predicate, result: true }, l).result } /** * Returns `true` if and only if the predicate function returns `true` * for all elements in the given list. * * @complexity O(n) */ export function every<A>(predicate: (a: A) => boolean): (l: List<A>) => boolean { return (l) => every_(l, predicate) } function someCb<A>(value: A, state: any): boolean { return !(state.result = state.predicate(value)) } /** * Returns true if and only if there exists an element in the list for * which the predicate returns true. * * @complexity O(n) */ export function some_<A>(l: List<A>, predicate: (a: A) => boolean): boolean { return foldlCb<A, PredState>(someCb, { predicate, result: false }, l).result } /** * Returns true if and only if there exists an element in the list for * which the predicate returns true. * * @complexity O(n) */ export function some<A>(predicate: (a: A) => boolean): (l: List<A>) => boolean { return (l) => some_(l, predicate) } /** * Returns `true` if and only if the predicate function returns * `false` for every element in the given list. * * @complexity O(n) */ export function none_<A>(l: List<A>, predicate: (a: A) => boolean): boolean { return !some_(l, predicate) } /** * Returns `true` if and only if the predicate function returns * `false` for every element in the given list. * * @complexity O(n) */ export function none<A>(predicate: (a: A) => boolean): (l: List<A>) => boolean { return (l) => none_(l, predicate) } function findCb<A>(value: A, state: PredState): boolean { if (state.predicate(value)) { state.result = O.some(value) return false } else { return true } } /** * Returns the _first_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function unsafeFind_<A>( l: List<A>, predicate: (a: A) => boolean ): A | undefined { return O.toUndefined(find_(l, predicate)) } /** * Returns the _first_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function unsafeFind<A>( predicate: (a: A) => boolean ): (l: List<A>) => A | undefined { return (l) => unsafeFind_(l, predicate) } /** * Returns the _first_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function find_<A>(l: List<A>, predicate: (a: A) => boolean): O.Option<A> { return foldlCb<A, PredState>(findCb, { predicate, result: O.none }, l).result } /** * Returns the _first_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function find<A>(predicate: (a: A) => boolean) { return (l: List<A>) => find_(l, predicate) } /** * Returns the _last_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function unsafeFindLast_<A>( l: List<A>, predicate: (a: A) => boolean ): A | undefined { return O.toUndefined(findLast_(l, predicate)) } /** * Returns the _last_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function unsafeFindLast<A>( predicate: (a: A) => boolean ): (l: List<A>) => A | undefined { return (l) => unsafeFindLast_(l, predicate) } /** * Returns the _last_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function findLast_<A>(l: List<A>, predicate: (a: A) => boolean): O.Option<A> { return foldrCb<A, PredState>(findCb, { predicate, result: O.none }, l).result } /** * Returns the _last_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function findLast<A>(predicate: (a: A) => boolean): (l: List<A>) => O.Option<A> { return (l) => findLast_(l, predicate) } type IndexOfState = { element: any found: boolean index: number } function indexOfCb(value: any, state: IndexOfState): boolean { ++state.index return !(state.found = elementEquals(value, state.element)) } /** * Returns the index of the _first_ element in the list that is equal * to the given element. If no such element is found `-1` is returned. * * @complexity O(n) */ export function indexOf_<A>(l: List<A>, element: A): number { const state = { element, found: false, index: -1 } foldlCb(indexOfCb, state, l) return state.found ? state.index : -1 } /** * Returns the index of the _first_ element in the list that is equal * to the given element. If no such element is found `-1` is returned. * * @complexity O(n) */ export function indexOf<A>(element: A): (l: List<A>) => number { return (l) => indexOf_(l, element) } /** * Returns the index of the _last_ element in the list that is equal * to the given element. If no such element is found `-1` is returned. * * @complexity O(n) */ export function lastIndexOf_<A>(l: List<A>, element: A): number { const state = { element, found: false, index: 0 } foldrCb(indexOfCb, state, l) return state.found ? l.length - state.index : -1 } /** * Returns the index of the _last_ element in the list that is equal * to the given element. If no such element is found `-1` is returned. * * @complexity O(n) */ export function lastIndexOf<A>(element: A): (l: List<A>) => number { return (l) => lastIndexOf_(l, element) } type FindIndexState = { predicate: (a: any) => boolean found: boolean index: number } function findIndexCb<A>(value: A, state: FindIndexState): boolean { ++state.index return !(state.found = state.predicate(value)) } /** * Returns the index of the `first` element for which the predicate * returns true. If no such element is found the function returns * `-1`. * * @complexity O(n) */ export function findIndex_<A>(l: List<A>, predicate: (a: A) => boolean): number { const { found, index } = foldlCb<A, FindIndexState>( findIndexCb, { predicate, found: false, index: -1 }, l ) return found ? index : -1 } /** * Returns the index of the `first` element for which the predicate * returns true. If no such element is found the function returns * `-1`. * * @complexity O(n) */ export function findIndex<A>(predicate: (a: A) => boolean): (l: List<A>) => number { return (l) => findIndex_(l, predicate) } type ContainsState = { element: any result: boolean } const containsState: ContainsState = { element: undefined,