function* genOf(a: A) { yield a } function* genMap(iterator: Iterator, mapping: (a: A, i: number) => B) { let n = -1 while (true) { const result = iterator.next() if (result.done) { break } n += 1 yield mapping(result.value, n) } } function* genChain(iterator: Iterator, mapping: (a: A) => Collection) { while (true) { const result = iterator.next() if (result.done) { break } const ib = mapping(result.value)[Symbol.iterator]() while (true) { const result = ib.next() if (result.done) { break } yield result.value } } } /** * Zips the values of both iterators with the provided zipper function * * @tsplus static Collection.Aspects zipWith * @tsplus pipeable Collection zipWith */ export function zipWith(that: Collection, zipper: (a: A, b: B) => C) { return (self: Collection): Collection => { // inspired from "Closing Iterables is a Leaky Abstraction" by Reginald Braithwaite // https://raganwald.com/2017/07/22/closing-iterables-is-a-leaky-abstraction.html return { [Symbol.iterator]() { let done = false const ia = self[Symbol.iterator]() const ib = that[Symbol.iterator]() return { next() { if (done) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this.return!() } const va = ia.next() const vb = ib.next() return va.done || vb.done ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.return!() : { done: false, value: zipper(va.value, vb.value) } }, return(value?: unknown) { if (!done) { done = true if (typeof ia.return === "function") { ia.return() } if (typeof ib.return === "function") { ib.return() } } return { done: true, value } } } } } } } /** * Maps the values of the iterator using the provided function * * @tsplus static Collection.Aspects map * @tsplus pipeable Collection map */ export function map(f: (a: A, k: number) => B) { return (self: Collection): Collection => { return { [Symbol.iterator]: () => genMap(self[Symbol.iterator](), f) } } } /** * Zips the two iterators into an iterator of a tuple * * @tsplus static Collection.Aspects zip * @tsplus pipeable Collection zip */ export function zip(that: Collection) { return (self: Collection): Collection => self.zipWith(that, (a, b) => [a, b]) } /** * Maps the iterator using the provided function and flatten its result * * @tsplus static Collection.Aspects flatMap * @tsplus pipeable Collection flatMap */ export function flatMap(f: (a: A) => Collection) { return (self: Collection): Collection => { return { [Symbol.iterator]: () => genChain(self[Symbol.iterator](), f) } } } /** * Applicative's apply * * @tsplus static Collection.Aspects ap * @tsplus pipeable Collection ap */ export function ap(fa: Collection) { return (fab: Collection<(a: A) => B>): Collection => fab.flatMap((f) => fa.map(f)) } /** * Creates an iterator of a single value * * @tsplus static Collection.Ops of */ export function of(a: A): Collection { return { [Symbol.iterator]: () => genOf(a) } } /** * Takes the fist n elements * * @tsplus static Collection.Aspects take * @tsplus pipeable Collection take */ export function take(n: number) { return (self: Collection): Collection => { return { *[Symbol.iterator]() { let i = 0 for (const x of self) { if (i++ >= n) { return } yield x } } } } } /** * Skips the first n elements * * @tsplus static Collection.Aspects skip * @tsplus pipeable Collection skip */ export function skip(n: number) { return (a: Collection): Collection => { return { *[Symbol.iterator]() { let i = 0 for (const x of a) { if (i++ >= n) { yield x } } } } } } /** * Empty iterator * * @tsplus static Collection.Ops never */ export const never: Collection = { // eslint-disable-next-line @typescript-eslint/no-empty-function *[Symbol.iterator]() {} } /** * Loops over the iterator accumulating a result using the provided function giving access to the element index * * @tsplus static Collection.Aspects reduceWithIndex * @tsplus pipeable Collection reduceWithIndex */ export function reduceWithIndex(b: B, f: (b: B, a: A, index: number) => B) { return (self: Collection): B => { let res = b let n = -1 const iterator = self[Symbol.iterator]() // eslint-disable-next-line no-constant-condition while (true) { const result = iterator.next() if (result.done) { break } n += 1 res = f(res, result.value, n) } return res } } /** * Loops over the iterator accumulating a result using the provided function * * @tsplus static Collection.Aspects reduce * @tsplus pipeable Collection reduce */ export function reduce(b: B, f: (b: B, a: A) => B) { return (self: Collection): B => { let res = b const iterator = self[Symbol.iterator]() // eslint-disable-next-line no-constant-condition while (true) { const result = iterator.next() if (result.done) { break } res = f(res, result.value) } return res } } /** * Loops over the iterator accumulating a result using the provided function and AssociativeIdentity giving access to the element index * * @tsplus static Collection.Aspects foldMapWithIndex * @tsplus pipeable Collection foldMapWithIndex */ export function foldMapWithIndex(M: AssociativeIdentity, f: (a: A, index: number) => M) { return (self: Collection): M => { let res = M.identity let n = -1 const iterator = self[Symbol.iterator]() // eslint-disable-next-line no-constant-condition while (true) { const result = iterator.next() if (result.done) { break } n += 1 res = M.combine(res, f(result.value, n)) } return res } } /** * Loops over the iterator accumulating a result using the provided function and AssociativeIdentity * * @tsplus static Collection.Aspects foldMap * @tsplus pipeable Collection foldMap */ export function foldMap(M: AssociativeIdentity, f: (a: A) => M) { return (self: Collection): M => { let res = M.identity const iterator = self[Symbol.iterator]() // eslint-disable-next-line no-constant-condition while (true) { const result = iterator.next() if (result.done) { break } res = M.combine(res, f(result.value)) } return res } } /** * Concats iterators together * * @tsplus pipeable-operator Collection & * @tsplus static Collection.Aspects concat * @tsplus pipeable Collection concat */ export function concat(that: Collection) { return (self: Collection): Collection => { return { *[Symbol.iterator]() { for (const x of self) { yield x } for (const x of that) { yield x } } } } } /** * Concats iterators together that are strictly of the same type * * @tsplus pipeable-operator Collection + */ export function concatOperator(that: Collection) { return (self: Collection): Collection => self.concat(that) } /** * Prepends a value to an iterator * * @tsplus static Collection.Aspects prepend * @tsplus pipeable Collection prepend */ export function prepend(that: B) { return (self: Collection): Collection => { return { *[Symbol.iterator]() { yield that for (const x of self) { yield x } } } } } /** * Prepends a value to an iterator * * @tsplus pipeable-operator Collection > */ export function prependOperator(self: Collection) { return (a: A): Collection => self.prepend(a) } /** * Prepends a value to an iterator of the same type * * @tsplus pipeable-operator Collection + 1.0 */ export function prependOperatorStrict(self: Collection) { return (a: A): Collection => a > self } /** * Appends a value to an iterator. * * @tsplus pipeable-operator Collection < * @tsplus static Collection.Aspects append * @tsplus pipeable Collection append */ export function append(that: B) { return (self: Collection): Collection => { return { *[Symbol.iterator]() { for (const x of self) { yield x } yield that } } } } /** * Appends a value to an iterator of the same type * * @tsplus pipeable-operator Collection + 1.0 */ export function appendOperatorStrict(a: A) { return (self: Collection): Collection => self.append(a) } /** * Flattens nested iterators * * @tsplus getter Collection flatten */ export function flatten(self: Collection>) { return self.flatMap(identity) } /** * Infinite sequence produced by repeated application of f to a * * @tsplus static Collection.Ops unfold */ export function unfold(a: A, f: (a: A) => A): Collection { return { *[Symbol.iterator]() { yield a let current = a while (true) { current = f(a) yield current } } } } /** * Compares each element of the iterators using the provided function * * @tsplus static Collection.Aspects equalsWith * @tsplus pipeable Collection equalsWith */ export function equalsWith(that: Collection, f: (a: A, b: B) => boolean) { return (self: Collection): boolean => { const leftIt = self[Symbol.iterator]() const rightIt = that[Symbol.iterator]() // eslint-disable-next-line no-constant-condition while (1) { const lnext = leftIt.next() const rnext = rightIt.next() if (lnext.done !== rnext.done) { return false } if (lnext.done) { return true } if (!f(lnext.value, rnext.value)) { return false } } throw new Error("Bug") } } /** * Compares the iterators using value equality * * @tsplus pipeable-operator Collection == * @tsplus static Collection.Aspects equals * @tsplus pipeable Collection equals */ export function equals(that: Collection): (self: Collection) => boolean export function equals(that: Collection): (self: Collection) => boolean export function equals(that: Collection) { return (self: Collection): boolean => self.equalsWith(that, Equals.equals) } /** * @tsplus static Collection.Ops make * @tsplus static Collection.Ops __call */ export function make(...as: A): Collection { return as } /** * @tsplus getter Collection toArray */ export function toArray(data: Collection): Array { return Array.from(data) }