/** * A data type for immutable linked lists representing ordered collections of elements of type `A`. * * This data type is optimal for last-in-first-out (LIFO), stack-like access patterns. If you need another access pattern, for example, random access or FIFO, consider using a collection more suited to this than `List`. * * **Performance** * * - Time: `List` has `O(1)` prepend and head/tail access. Most other operations are `O(n)` on the number of elements in the list. This includes the index-based lookup of elements, `length`, `append` and `reverse`. * - Space: `List` implements structural sharing of the tail list. This means that many operations are either zero- or constant-memory cost. * * @since 2.0.0 */ /** * This file is ported from * * Scala (https://www.scala-lang.org) * * Copyright EPFL and Lightbend, Inc. * * Licensed under Apache License 2.0 * (http://www.apache.org/licenses/LICENSE-2.0). */ import * as Arr from "./Array.js" import * as Chunk from "./Chunk.js" import * as Either from "./Either.js" import * as Equal from "./Equal.js" import * as Equivalence from "./Equivalence.js" import { dual, identity, unsafeCoerce } from "./Function.js" import * as Hash from "./Hash.js" import { format, type Inspectable, NodeInspectSymbol, toJSON } from "./Inspectable.js" import type { nonEmpty, NonEmptyIterable } from "./NonEmptyIterable.js" import * as Option from "./Option.js" import type { Pipeable } from "./Pipeable.js" import { pipeArguments } from "./Pipeable.js" import { hasProperty, type Predicate, type Refinement } from "./Predicate.js" import type { NoInfer } from "./Types.js" /** * Represents an immutable linked list of elements of type `A`. * * A `List` is optimal for last-in-first-out (LIFO), stack-like access patterns. * If you need another access pattern, for example, random access or FIFO, * consider using a collection more suited for that other than `List`. * * @since 2.0.0 * @category models */ export type List = Cons | Nil /** * @since 2.0.0 * @category symbol */ export const TypeId: unique symbol = Symbol.for("effect/List") /** * @since 2.0.0 * @category symbol */ export type TypeId = typeof TypeId /** * @since 2.0.0 * @category models */ export interface Nil extends Iterable, Equal.Equal, Pipeable, Inspectable { readonly [TypeId]: TypeId readonly _tag: "Nil" } /** * @since 2.0.0 * @category models */ export interface Cons extends NonEmptyIterable, Equal.Equal, Pipeable, Inspectable { readonly [TypeId]: TypeId readonly _tag: "Cons" readonly head: A readonly tail: List } /** * Converts the specified `List` to an `Array`. * * @category conversions * @since 2.0.0 */ export const toArray = (self: List): Array => Arr.fromIterable(self) /** * @category equivalence * @since 2.0.0 */ export const getEquivalence = (isEquivalent: Equivalence.Equivalence): Equivalence.Equivalence> => Equivalence.mapInput(Arr.getEquivalence(isEquivalent), toArray) const _equivalence = getEquivalence(Equal.equals) const ConsProto: Omit, "head" | "tail" | typeof nonEmpty> = { [TypeId]: TypeId, _tag: "Cons", toString(this: Cons) { return format(this.toJSON()) }, toJSON(this: Cons) { return { _id: "List", _tag: "Cons", values: toArray(this).map(toJSON) } }, [NodeInspectSymbol]() { return this.toJSON() }, [Equal.symbol](this: Cons, that: unknown): boolean { return isList(that) && this._tag === that._tag && _equivalence(this, that) }, [Hash.symbol](this: Cons): number { return Hash.cached(this, Hash.array(toArray(this))) }, [Symbol.iterator](this: Cons): Iterator { let done = false // eslint-disable-next-line @typescript-eslint/no-this-alias let self: List = this return { next() { if (done) { return this.return!() } if (self._tag === "Nil") { done = true return this.return!() } const value: unknown = self.head self = self.tail return { done, value } }, return(value?: unknown) { if (!done) { done = true } return { done: true, value } } } }, pipe() { return pipeArguments(this, arguments) } } interface MutableCons extends Cons { head: A tail: List } const makeCons = (head: A, tail: List): MutableCons => { const cons = Object.create(ConsProto) cons.head = head cons.tail = tail return cons } const NilHash = Hash.string("Nil") const NilProto: Nil = { [TypeId]: TypeId, _tag: "Nil", toString() { return format(this.toJSON()) }, toJSON() { return { _id: "List", _tag: "Nil" } }, [NodeInspectSymbol]() { return this.toJSON() }, [Hash.symbol](): number { return NilHash }, [Equal.symbol](that: unknown): boolean { return isList(that) && this._tag === that._tag }, [Symbol.iterator](): Iterator { return { next() { return { done: true, value: undefined } } } }, pipe() { return pipeArguments(this, arguments) } } as const const _Nil = Object.create(NilProto) as Nil /** * Returns `true` if the specified value is a `List`, `false` otherwise. * * @since 2.0.0 * @category refinements */ export const isList: { /** * Returns `true` if the specified value is a `List`, `false` otherwise. * * @since 2.0.0 * @category refinements */ (u: Iterable): u is List /** * Returns `true` if the specified value is a `List`, `false` otherwise. * * @since 2.0.0 * @category refinements */ (u: unknown): u is List } = (u: unknown): u is List => hasProperty(u, TypeId) /** * Returns `true` if the specified value is a `List.Nil`, `false` otherwise. * * @since 2.0.0 * @category refinements */ export const isNil = (self: List): self is Nil => self._tag === "Nil" /** * Returns `true` if the specified value is a `List.Cons`, `false` otherwise. * * @since 2.0.0 * @category refinements */ export const isCons = (self: List): self is Cons => self._tag === "Cons" /** * Returns the number of elements contained in the specified `List` * * @since 2.0.0 * @category getters */ export const size = (self: List): number => { let these = self let len = 0 while (!isNil(these)) { len += 1 these = these.tail } return len } /** * Constructs a new empty `List`. * * @since 2.0.0 * @category constructors */ export const nil = (): List => _Nil /** * Constructs a new `List.Cons` from the specified `head` and `tail` values. * * @since 2.0.0 * @category constructors */ export const cons = (head: A, tail: List): Cons => makeCons(head, tail) /** * Constructs a new empty `List`. * * Alias of {@link nil}. * * @since 2.0.0 * @category constructors */ export const empty = nil /** * Constructs a new `List` from the specified value. * * @since 2.0.0 * @category constructors */ export const of = (value: A): Cons => makeCons(value, _Nil) /** * Creates a new `List` from an iterable collection of values. * * @since 2.0.0 * @category constructors */ export const fromIterable = (prefix: Iterable): List => { const iterator = prefix[Symbol.iterator]() let next: IteratorResult if ((next = iterator.next()) && !next.done) { const result = makeCons(next.value, _Nil) let curr = result while ((next = iterator.next()) && !next.done) { const temp = makeCons(next.value, _Nil) curr.tail = temp curr = temp } return result } else { return _Nil } } /** * Constructs a new `List` from the specified values. * * @since 2.0.0 * @category constructors */ export const make = ]>( ...elements: Elements ): Cons => fromIterable(elements) as any /** * Appends the specified element to the end of the `List`, creating a new `Cons`. * * @category concatenating * @since 2.0.0 */ export const append: { /** * Appends the specified element to the end of the `List`, creating a new `Cons`. * * @category concatenating * @since 2.0.0 */ (element: B): (self: List) => Cons /** * Appends the specified element to the end of the `List`, creating a new `Cons`. * * @category concatenating * @since 2.0.0 */ (self: List, element: B): Cons } = dual(2, (self: List, element: B): Cons => appendAll(self, of(element))) /** * Concatenates two lists, combining their elements. * If either list is non-empty, the result is also a non-empty list. * * @example * ```ts * import * as assert from "node:assert" * import { List } from "effect" * * assert.deepStrictEqual( * List.make(1, 2).pipe(List.appendAll(List.make("a", "b")), List.toArray), * [1, 2, "a", "b"] * ) * ``` * * @category concatenating * @since 2.0.0 */ export const appendAll: { /** * Concatenates two lists, combining their elements. * If either list is non-empty, the result is also a non-empty list. * * @example * ```ts * import * as assert from "node:assert" * import { List } from "effect" * * assert.deepStrictEqual( * List.make(1, 2).pipe(List.appendAll(List.make("a", "b")), List.toArray), * [1, 2, "a", "b"] * ) * ``` * * @category concatenating * @since 2.0.0 */ , T extends List>(that: T): (self: S) => List.OrNonEmpty | List.Infer> /** * Concatenates two lists, combining their elements. * If either list is non-empty, the result is also a non-empty list. * * @example * ```ts * import * as assert from "node:assert" * import { List } from "effect" * * assert.deepStrictEqual( * List.make(1, 2).pipe(List.appendAll(List.make("a", "b")), List.toArray), * [1, 2, "a", "b"] * ) * ``` * * @category concatenating * @since 2.0.0 */ (self: List, that: Cons): Cons /** * Concatenates two lists, combining their elements. * If either list is non-empty, the result is also a non-empty list. * * @example * ```ts * import * as assert from "node:assert" * import { List } from "effect" * * assert.deepStrictEqual( * List.make(1, 2).pipe(List.appendAll(List.make("a", "b")), List.toArray), * [1, 2, "a", "b"] * ) * ``` * * @category concatenating * @since 2.0.0 */ (self: Cons, that: List): Cons /** * Concatenates two lists, combining their elements. * If either list is non-empty, the result is also a non-empty list. * * @example * ```ts * import * as assert from "node:assert" * import { List } from "effect" * * assert.deepStrictEqual( * List.make(1, 2).pipe(List.appendAll(List.make("a", "b")), List.toArray), * [1, 2, "a", "b"] * ) * ``` * * @category concatenating * @since 2.0.0 */ (self: List, that: List): List } = dual(2, (self: List, that: List): List => prependAll(that, self)) /** * Prepends the specified element to the beginning of the list. * * @category concatenating * @since 2.0.0 */ export const prepend: { /** * Prepends the specified element to the beginning of the list. * * @category concatenating * @since 2.0.0 */ (element: B): (self: List) => Cons /** * Prepends the specified element to the beginning of the list. * * @category concatenating * @since 2.0.0 */ (self: List, element: B): Cons } = dual(2, (self: List, element: B): Cons => cons(element, self)) /** * Prepends the specified prefix list to the beginning of the specified list. * If either list is non-empty, the result is also a non-empty list. * * @example * ```ts * import * as assert from "node:assert" * import { List } from "effect" * * assert.deepStrictEqual( * List.make(1, 2).pipe(List.prependAll(List.make("a", "b")), List.toArray), * ["a", "b", 1, 2] * ) * ``` * * @category concatenating * @since 2.0.0 */ export const prependAll: { /** * Prepends the specified prefix list to the beginning of the specified list. * If either list is non-empty, the result is also a non-empty list. * * @example * ```ts * import * as assert from "node:assert" * import { List } from "effect" * * assert.deepStrictEqual( * List.make(1, 2).pipe(List.prependAll(List.make("a", "b")), List.toArray), * ["a", "b", 1, 2] * ) * ``` * * @category concatenating * @since 2.0.0 */ , T extends List>(that: T): (self: S) => List.OrNonEmpty | List.Infer> /** * Prepends the specified prefix list to the beginning of the specified list. * If either list is non-empty, the result is also a non-empty list. * * @example * ```ts * import * as assert from "node:assert" * import { List } from "effect" * * assert.deepStrictEqual( * List.make(1, 2).pipe(List.prependAll(List.make("a", "b")), List.toArray), * ["a", "b", 1, 2] * ) * ``` * * @category concatenating * @since 2.0.0 */ (self: List, that: Cons): Cons /** * Prepends the specified prefix list to the beginning of the specified list. * If either list is non-empty, the result is also a non-empty list. * * @example * ```ts * import * as assert from "node:assert" * import { List } from "effect" * * assert.deepStrictEqual( * List.make(1, 2).pipe(List.prependAll(List.make("a", "b")), List.toArray), * ["a", "b", 1, 2] * ) * ``` * * @category concatenating * @since 2.0.0 */ (self: Cons, that: List): Cons /** * Prepends the specified prefix list to the beginning of the specified list. * If either list is non-empty, the result is also a non-empty list. * * @example * ```ts * import * as assert from "node:assert" * import { List } from "effect" * * assert.deepStrictEqual( * List.make(1, 2).pipe(List.prependAll(List.make("a", "b")), List.toArray), * ["a", "b", 1, 2] * ) * ``` * * @category concatenating * @since 2.0.0 */ (self: List, that: List): List } = dual(2, (self: List, prefix: List): List => { if (isNil(self)) { return prefix } else if (isNil(prefix)) { return self } else { const result = makeCons(prefix.head, self) let curr = result let that = prefix.tail while (!isNil(that)) { const temp = makeCons(that.head, self) curr.tail = temp curr = temp that = that.tail } return result } }) /** * Prepends the specified prefix list (in reverse order) to the beginning of the * specified list. * * @category concatenating * @since 2.0.0 */ export const prependAllReversed: { /** * Prepends the specified prefix list (in reverse order) to the beginning of the * specified list. * * @category concatenating * @since 2.0.0 */ (prefix: List): (self: List) => List /** * Prepends the specified prefix list (in reverse order) to the beginning of the * specified list. * * @category concatenating * @since 2.0.0 */ (self: List, prefix: List): List } = dual(2, (self: List, prefix: List): List => { let out: List = self let pres = prefix while (isCons(pres)) { out = makeCons(pres.head, out) pres = pres.tail } return out }) /** * Drops the first `n` elements from the specified list. * * @since 2.0.0 * @category combinators */ export const drop: { /** * Drops the first `n` elements from the specified list. * * @since 2.0.0 * @category combinators */ (n: number): (self: List) => List /** * Drops the first `n` elements from the specified list. * * @since 2.0.0 * @category combinators */ (self: List, n: number): List } = dual(2, (self: List, n: number): List => { if (n <= 0) { return self } if (n >= size(self)) { return _Nil } let these = self let i = 0 while (!isNil(these) && i < n) { these = these.tail i += 1 } return these }) /** * Check if a predicate holds true for every `List` element. * * @since 2.0.0 * @category elements */ export const every: { /** * Check if a predicate holds true for every `List` element. * * @since 2.0.0 * @category elements */ (refinement: Refinement, B>): (self: List) => self is List /** * Check if a predicate holds true for every `List` element. * * @since 2.0.0 * @category elements */ (predicate: Predicate): (self: List) => boolean /** * Check if a predicate holds true for every `List` element. * * @since 2.0.0 * @category elements */ (self: List, refinement: Refinement): self is List /** * Check if a predicate holds true for every `List` element. * * @since 2.0.0 * @category elements */ (self: List, predicate: Predicate): boolean } = dual(2, (self: List, refinement: Refinement): self is List => { for (const a of self) { if (!refinement(a)) { return false } } return true }) /** * Check if a predicate holds true for some `List` element. * * @since 2.0.0 * @category elements */ export const some: { /** * Check if a predicate holds true for some `List` element. * * @since 2.0.0 * @category elements */ (predicate: Predicate>): (self: List) => self is Cons /** * Check if a predicate holds true for some `List` element. * * @since 2.0.0 * @category elements */ (self: List, predicate: Predicate): self is Cons } = dual(2, (self: List, predicate: Predicate): self is Cons => { let these = self while (!isNil(these)) { if (predicate(these.head)) { return true } these = these.tail } return false }) /** * Filters a list using the specified predicate. * * @since 2.0.0 * @category combinators */ export const filter: { /** * Filters a list using the specified predicate. * * @since 2.0.0 * @category combinators */ (refinement: Refinement, B>): (self: List) => List /** * Filters a list using the specified predicate. * * @since 2.0.0 * @category combinators */ (predicate: Predicate>): (self: List) => List /** * Filters a list using the specified predicate. * * @since 2.0.0 * @category combinators */ (self: List, refinement: Refinement): List /** * Filters a list using the specified predicate. * * @since 2.0.0 * @category combinators */ (self: List, predicate: Predicate): List } = dual(2, (self: List, predicate: Predicate): List => noneIn(self, predicate, false)) // everything seen so far is not included const noneIn = ( self: List, predicate: Predicate, isFlipped: boolean ): List => { while (true) { if (isNil(self)) { return _Nil } else { if (predicate(self.head) !== isFlipped) { return allIn(self, self.tail, predicate, isFlipped) } else { self = self.tail } } } } // everything from 'start' is included, if everything from this point is in we can return the origin // start otherwise if we discover an element that is out we must create a new partial list. const allIn = ( start: List, remaining: List, predicate: Predicate, isFlipped: boolean ): List => { while (true) { if (isNil(remaining)) { return start } else { if (predicate(remaining.head) !== isFlipped) { remaining = remaining.tail } else { return partialFill(start, remaining, predicate, isFlipped) } } } } // we have seen elements that should be included then one that should be excluded, start building const partialFill = ( origStart: List, firstMiss: List, predicate: Predicate, isFlipped: boolean ): List => { const newHead = makeCons(unsafeHead(origStart)!, _Nil) let toProcess = unsafeTail(origStart)! as Cons let currentLast = newHead // we know that all elements are :: until at least firstMiss.tail while (!(toProcess === firstMiss)) { const newElem = makeCons(unsafeHead(toProcess)!, _Nil) currentLast.tail = newElem currentLast = unsafeCoerce(newElem) toProcess = unsafeCoerce(toProcess.tail) } // at this point newHead points to a list which is a duplicate of all the 'in' elements up to the first miss. // currentLast is the last element in that list. // now we are going to try and share as much of the tail as we can, only moving elements across when we have to. let next = firstMiss.tail let nextToCopy: Cons = unsafeCoerce(next) // the next element we would need to copy to our list if we cant share. while (!isNil(next)) { // generally recommended is next.isNonEmpty but this incurs an extra method call. const head = unsafeHead(next)! if (predicate(head) !== isFlipped) { next = next.tail } else { // its not a match - do we have outstanding elements? while (!(nextToCopy === next)) { const newElem = makeCons(unsafeHead(nextToCopy)!, _Nil) currentLast.tail = newElem currentLast = newElem nextToCopy = unsafeCoerce(nextToCopy.tail) } nextToCopy = unsafeCoerce(next.tail) next = next.tail } } // we have remaining elements - they are unchanged attach them to the end if (!isNil(nextToCopy)) { currentLast.tail = nextToCopy } return newHead } /** * Filters and maps a list using the specified partial function. The resulting * list may be smaller than the input list due to the possibility of the partial * function not being defined for some elements. * * @since 2.0.0 * @category combinators */ export const filterMap: { /** * Filters and maps a list using the specified partial function. The resulting * list may be smaller than the input list due to the possibility of the partial * function not being defined for some elements. * * @since 2.0.0 * @category combinators */ (f: (a: A) => Option.Option): (self: List) => List /** * Filters and maps a list using the specified partial function. The resulting * list may be smaller than the input list due to the possibility of the partial * function not being defined for some elements. * * @since 2.0.0 * @category combinators */ (self: List, f: (a: A) => Option.Option): List } = dual(2, (self: List, f: (a: A) => Option.Option): List => { const bs: Array = [] for (const a of self) { const oa = f(a) if (Option.isSome(oa)) { bs.push(oa.value) } } return fromIterable(bs) }) /** * Removes all `None` values from the specified list. * * @since 2.0.0 * @category combinators */ export const compact = (self: List>): List => filterMap(self, identity) /** * Returns the first element that satisfies the specified * predicate, or `None` if no such element exists. * * @category elements * @since 2.0.0 */ export const findFirst: { /** * Returns the first element that satisfies the specified * predicate, or `None` if no such element exists. * * @category elements * @since 2.0.0 */ (refinement: Refinement, B>): (self: List) => Option.Option /** * Returns the first element that satisfies the specified * predicate, or `None` if no such element exists. * * @category elements * @since 2.0.0 */ (predicate: Predicate>): (self: List) => Option.Option /** * Returns the first element that satisfies the specified * predicate, or `None` if no such element exists. * * @category elements * @since 2.0.0 */ (self: List, refinement: Refinement): Option.Option /** * Returns the first element that satisfies the specified * predicate, or `None` if no such element exists. * * @category elements * @since 2.0.0 */ (self: List, predicate: Predicate): Option.Option } = dual(2, (self: List, predicate: Predicate): Option.Option => { let these = self while (!isNil(these)) { if (predicate(these.head)) { return Option.some(these.head) } these = these.tail } return Option.none() }) /** * Applies a function to each element in a list and returns a new list containing the concatenated mapped elements. * * @since 2.0.0 * @category sequencing */ export const flatMap: { /** * Applies a function to each element in a list and returns a new list containing the concatenated mapped elements. * * @since 2.0.0 * @category sequencing */ , T extends List>(f: (a: List.Infer, i: number) => T): (self: S) => List.AndNonEmpty> /** * Applies a function to each element in a list and returns a new list containing the concatenated mapped elements. * * @since 2.0.0 * @category sequencing */ (self: Cons, f: (a: A, i: number) => Cons): Cons /** * Applies a function to each element in a list and returns a new list containing the concatenated mapped elements. * * @since 2.0.0 * @category sequencing */ (self: List, f: (a: A, i: number) => List): List } = dual(2, (self: List, f: (a: A) => List): List => { let rest = self let head: MutableCons | undefined = undefined let tail: MutableCons | undefined = undefined while (!isNil(rest)) { let bs = f(rest.head) while (!isNil(bs)) { const next = makeCons(bs.head, _Nil) if (tail === undefined) { head = next } else { tail.tail = next } tail = next bs = bs.tail } rest = rest.tail } if (head === undefined) { return _Nil } return head }) /** * Applies the specified function to each element of the `List`. * * @since 2.0.0 * @category combinators */ export const forEach: { /** * Applies the specified function to each element of the `List`. * * @since 2.0.0 * @category combinators */ (f: (a: A) => B): (self: List) => void /** * Applies the specified function to each element of the `List`. * * @since 2.0.0 * @category combinators */ (self: List, f: (a: A) => B): void } = dual(2, (self: List, f: (a: A) => B): void => { let these = self while (!isNil(these)) { f(these.head) these = these.tail } }) /** * Returns the first element of the specified list, or `None` if the list is * empty. * * @since 2.0.0 * @category getters */ export const head = (self: List): Option.Option => isNil(self) ? Option.none() : Option.some(self.head) /** * Returns the last element of the specified list, or `None` if the list is * empty. * * @since 2.0.0 * @category getters */ export const last = (self: List): Option.Option => isNil(self) ? Option.none() : Option.some(unsafeLast(self)!) /** * @since 2.0.0 */ export declare namespace List { /** * @since 2.0.0 */ export type Infer> = S extends List ? A : never /** * @since 2.0.0 */ export type With, A> = S extends Cons ? Cons : List /** * @since 2.0.0 */ export type OrNonEmpty, T extends List, A> = S extends Cons ? Cons : T extends Cons ? Cons : List /** * @since 2.0.0 */ export type AndNonEmpty, T extends List, A> = S extends Cons ? T extends Cons ? Cons : List : List } /** * Applies the specified mapping function to each element of the list. * * @since 2.0.0 * @category mapping */ export const map: { /** * Applies the specified mapping function to each element of the list. * * @since 2.0.0 * @category mapping */ , B>(f: (a: List.Infer, i: number) => B): (self: S) => List.With /** * Applies the specified mapping function to each element of the list. * * @since 2.0.0 * @category mapping */ , B>(self: S, f: (a: List.Infer, i: number) => B): List.With } = dual(2, (self: List, f: (a: A, i: number) => B): List => { if (isNil(self)) { return self as unknown as List } else { let i = 0 const head = makeCons(f(self.head, i++), _Nil) let nextHead = head let rest = self.tail while (!isNil(rest)) { const next = makeCons(f(rest.head, i++), _Nil) nextHead.tail = next nextHead = next rest = rest.tail } return head } }) /** * Partition a list into two lists, where the first list contains all elements * that did not satisfy the specified predicate, and the second list contains * all elements that did satisfy the specified predicate. * * @since 2.0.0 * @category combinators */ export const partition: { /** * Partition a list into two lists, where the first list contains all elements * that did not satisfy the specified predicate, and the second list contains * all elements that did satisfy the specified predicate. * * @since 2.0.0 * @category combinators */ (refinement: Refinement, B>): (self: List) => [excluded: List>, satisfying: List] /** * Partition a list into two lists, where the first list contains all elements * that did not satisfy the specified predicate, and the second list contains * all elements that did satisfy the specified predicate. * * @since 2.0.0 * @category combinators */ (predicate: Predicate>): (self: List) => [excluded: List, satisfying: List] /** * Partition a list into two lists, where the first list contains all elements * that did not satisfy the specified predicate, and the second list contains * all elements that did satisfy the specified predicate. * * @since 2.0.0 * @category combinators */ (self: List, refinement: Refinement): [excluded: List>, satisfying: List] /** * Partition a list into two lists, where the first list contains all elements * that did not satisfy the specified predicate, and the second list contains * all elements that did satisfy the specified predicate. * * @since 2.0.0 * @category combinators */ (self: List, predicate: Predicate): [excluded: List, satisfying: List] } = dual(2, (self: List, predicate: Predicate): [excluded: List, satisfying: List] => { const left: Array = [] const right: Array = [] for (const a of self) { if (predicate(a)) { right.push(a) } else { left.push(a) } } return [fromIterable(left), fromIterable(right)] }) /** * Partition a list into two lists, where the first list contains all elements * for which the specified function returned a `Left`, and the second list * contains all elements for which the specified function returned a `Right`. * * @since 2.0.0 * @category combinators */ export const partitionMap: { /** * Partition a list into two lists, where the first list contains all elements * for which the specified function returned a `Left`, and the second list * contains all elements for which the specified function returned a `Right`. * * @since 2.0.0 * @category combinators */ (f: (a: A) => Either.Either): (self: List) => [left: List, right: List] /** * Partition a list into two lists, where the first list contains all elements * for which the specified function returned a `Left`, and the second list * contains all elements for which the specified function returned a `Right`. * * @since 2.0.0 * @category combinators */ (self: List, f: (a: A) => Either.Either): [left: List, right: List] } = dual(2, (self: List, f: (a: A) => Either.Either): [left: List, right: List] => { const left: Array = [] const right: Array = [] for (const a of self) { const e = f(a) if (Either.isLeft(e)) { left.push(e.left) } else { right.push(e.right) } } return [fromIterable(left), fromIterable(right)] }) /** * Folds over the elements of the list using the specified function, using the * specified initial value. * * @since 2.0.0 * @category folding */ export const reduce: { /** * Folds over the elements of the list using the specified function, using the * specified initial value. * * @since 2.0.0 * @category folding */ (zero: Z, f: (b: Z, a: A) => Z): (self: List) => Z /** * Folds over the elements of the list using the specified function, using the * specified initial value. * * @since 2.0.0 * @category folding */ (self: List, zero: Z, f: (b: Z, a: A) => Z): Z } = dual(3, (self: List, zero: Z, f: (b: Z, a: A) => Z): Z => { let acc = zero let these = self while (!isNil(these)) { acc = f(acc, these.head) these = these.tail } return acc }) /** * Folds over the elements of the list using the specified function, beginning * with the last element of the list, using the specified initial value. * * @since 2.0.0 * @category folding */ export const reduceRight: { /** * Folds over the elements of the list using the specified function, beginning * with the last element of the list, using the specified initial value. * * @since 2.0.0 * @category folding */ (zero: Z, f: (accumulator: Z, value: A) => Z): (self: List) => Z /** * Folds over the elements of the list using the specified function, beginning * with the last element of the list, using the specified initial value. * * @since 2.0.0 * @category folding */ (self: List, zero: Z, f: (accumulator: Z, value: A) => Z): Z } = dual(3, (self: List, zero: Z, f: (accumulator: Z, value: A) => Z): Z => { let acc = zero let these = reverse(self) while (!isNil(these)) { acc = f(acc, these.head) these = these.tail } return acc }) /** * Returns a new list with the elements of the specified list in reverse order. * * @since 2.0.0 * @category elements */ export const reverse = (self: List): List => { let result = empty() let these = self while (!isNil(these)) { result = prepend(result, these.head) these = these.tail } return result } /** * Splits the specified list into two lists at the specified index. * * @since 2.0.0 * @category combinators */ export const splitAt: { /** * Splits the specified list into two lists at the specified index. * * @since 2.0.0 * @category combinators */ (n: number): (self: List) => [beforeIndex: List, fromIndex: List] /** * Splits the specified list into two lists at the specified index. * * @since 2.0.0 * @category combinators */ (self: List, n: number): [beforeIndex: List, fromIndex: List] } = dual(2, (self: List, n: number): [List, List] => [take(self, n), drop(self, n)]) /** * Returns the tail of the specified list, or `None` if the list is empty. * * @since 2.0.0 * @category getters */ export const tail = (self: List): Option.Option> => isNil(self) ? Option.none() : Option.some(self.tail) /** * Takes the specified number of elements from the beginning of the specified * list. * * @since 2.0.0 * @category combinators */ export const take: { /** * Takes the specified number of elements from the beginning of the specified * list. * * @since 2.0.0 * @category combinators */ (n: number): (self: List) => List /** * Takes the specified number of elements from the beginning of the specified * list. * * @since 2.0.0 * @category combinators */ (self: List, n: number): List } = dual(2, (self: List, n: number): List => { if (n <= 0) { return _Nil } if (n >= size(self)) { return self } let these = make(unsafeHead(self)) let current = unsafeTail(self)! for (let i = 1; i < n; i++) { these = makeCons(unsafeHead(current), these) current = unsafeTail(current!) } return reverse(these) }) /** * Converts the specified `List` to a `Chunk`. * * @since 2.0.0 * @category conversions */ export const toChunk = (self: List): Chunk.Chunk => Chunk.fromIterable(self) const getExpectedListToBeNonEmptyErrorMessage = "Expected List to be non-empty" /** * Unsafely returns the first element of the specified `List`. * * @since 2.0.0 * @category unsafe */ export const unsafeHead = (self: List): A => { if (isNil(self)) { throw new Error(getExpectedListToBeNonEmptyErrorMessage) } return self.head } /** * Unsafely returns the last element of the specified `List`. * * @since 2.0.0 * @category unsafe */ export const unsafeLast = (self: List): A => { if (isNil(self)) { throw new Error(getExpectedListToBeNonEmptyErrorMessage) } let these = self let scout = self.tail while (!isNil(scout)) { these = scout scout = scout.tail } return these.head } /** * Unsafely returns the tail of the specified `List`. * * @since 2.0.0 * @category unsafe */ export const unsafeTail = (self: List): List => { if (isNil(self)) { throw new Error(getExpectedListToBeNonEmptyErrorMessage) } return self.tail }