/**
* 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**
* Prepends the specified element to the beginning of the list.
*
* @category concatenating
* @since 2.0.0
*/
A | B>(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**
* 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
*/
A | B>(prefix.head, self)
let curr = result
let that = prefix.tail
while (!isNil(that)) {
const temp = makeCons**
* 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
*/
A | B>(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
}