import * as Ei from 'fp-ts/Either' import * as Eq from 'fp-ts/Eq' import { flow, pipe } from 'fp-ts/function' import * as N from 'fp-ts/number' import * as O from 'fp-ts/Option' import * as RA from 'fp-ts/ReadonlyArray' import * as Se from 'fp-ts/Separated' import * as St from 'fp-ts/string' import { Alt, Alternative, append, Applicative, Apply, Chain, chunksOf, Compactable, deleteAt, dropLeft, dropLeftWhile, duplicate, elem, exp, fibonacci, filter, filterMap, filterMapWithIndex, filterWithIndex, findFirstIndex, findLast, findLastIndex, findLastMap, flatten, foldMapWithIndex, FromIO, fromReadonlyArray, fromReadonlyRecord, Functor, FunctorWithIndex, getEq, getMonoid, getOrd, head, init, insertAt, intersperse, isEmpty, isNonEmpty, lefts, lookup, makeBy, map, matchLeft, matchRight, Monad, of, partition, partitionMap, partitionMapWithIndex, partitionWithIndex, prepend, prime, range, replicate, reverse, rights, rotate, scanRight, sequence, sieve, size, spanLeft, splitAt, tail, takeLeft, takeLeftWhile, toReadonlyArray, unfold, uniq, unzip, updateAt, wilt, wither, zip, } from './Yield' describe('Yield', () => { describe('makeBy', () => { it('should create a generator using a function', () => { expect( pipe( makeBy((i) => Math.sin((i * Math.PI) / 4)), takeLeft(8), toReadonlyArray, ), ).toStrictEqual([ Math.sin((0 * Math.PI) / 4), Math.sin(Math.PI / 4), Math.sin((2 * Math.PI) / 4), Math.sin((3 * Math.PI) / 4), Math.sin((4 * Math.PI) / 4), Math.sin((5 * Math.PI) / 4), Math.sin((6 * Math.PI) / 4), Math.sin((7 * Math.PI) / 4), ]) }) }) describe('range', () => { it('should return a list of numbers', () => { expect(pipe(range(0), takeLeft(5), toReadonlyArray)).toStrictEqual([ 0, 1, 2, 3, 4, ]) expect(pipe(range(1138), takeLeft(5), toReadonlyArray)).toStrictEqual([ 1138, 1139, 1140, 1141, 1142, ]) }) it('should allow starting from a negative number', () => { expect(pipe(range(-1337), takeLeft(5), toReadonlyArray)).toStrictEqual([ -1337, -1336, -1335, -1334, -1333, ]) }) it('should allow setting a top boundary', () => { expect(pipe(range(0, 9), toReadonlyArray)).toStrictEqual([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ]) expect(pipe(range(42, 49), toReadonlyArray)).toStrictEqual([ 42, 43, 44, 45, 46, 47, 48, 49, ]) }) it('should support a top boundary smaller than the bottom one', () => { expect(pipe(range(42, -Infinity), toReadonlyArray)).toStrictEqual([42]) }) }) describe('replicate', () => { it('should replicate the specified element', () => { expect(pipe(replicate(42), takeLeft(5), toReadonlyArray)).toStrictEqual([ 42, 42, 42, 42, 42, ]) }) }) describe('fromReadonlyArray', () => { it('should transform an array into a generator', () => { expect( pipe(fromReadonlyArray([42, 1138, 1337]), toReadonlyArray), ).toStrictEqual([42, 1138, 1337]) }) }) describe('fromReadonlyRecord', () => { it('should transform a record into a generator', () => { expect( pipe( fromReadonlyRecord({ foo: 42, bar: 1138, max: 1337 }), toReadonlyArray, ), ).toStrictEqual([ ['bar', 1138], ['foo', 42], ['max', 1337], ]) }) }) describe('prime', () => { it('should return a list of prime numbers', () => { expect(pipe(prime, takeLeft(5), toReadonlyArray)).toStrictEqual([ 2, 3, 5, 7, 11, ]) }) }) describe('exp', () => { it('should return the exponential function', () => { expect(pipe(exp, takeLeft(5), toReadonlyArray)).toStrictEqual([ Math.exp(0), Math.exp(1), Math.exp(2), Math.exp(3), Math.exp(4), ]) }) }) describe('fibonacci', () => { it('should return the Fibonacci sequence', () => { expect(pipe(fibonacci, takeLeft(10), toReadonlyArray)).toStrictEqual([ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ]) }) }) describe('flatten', () => { it('should flatten nested generators', () => { expect( pipe( fromReadonlyArray([0, 1, 2]), // eslint-disable-next-line fp-ts/prefer-chain map((a) => fromReadonlyArray([3 * a, 3 * a + 1, 3 * a + 2])), flatten, toReadonlyArray, ), ).toStrictEqual([0, 1, 2, 3, 4, 5, 6, 7, 8]) }) }) describe('prepend', () => { it('should add an element at the top of the list', () => { expect( pipe(prime, prepend(42), takeLeft(5), toReadonlyArray), ).toStrictEqual([42, 2, 3, 5, 7]) }) }) describe('append', () => { it('should add an element at the bottom of the list', () => { expect( pipe(prime, takeLeft(5), append(42), toReadonlyArray), ).toStrictEqual([2, 3, 5, 7, 11, 42]) }) }) describe('takeLeft', () => { it('should select only specified elements', () => { const x = pipe(range(0), takeLeft(5), toReadonlyArray) expect(x).toHaveLength(5) expect(x).toStrictEqual([0, 1, 2, 3, 4]) }) it('should handle negative numbers', () => { const x = pipe(range(0), takeLeft(-Infinity), toReadonlyArray) expect(x).toHaveLength(0) }) }) describe('takeLeftWhile', () => { it('should select elements according to a predicate', () => { expect( pipe( prime, takeLeftWhile((n) => n <= 10), toReadonlyArray, ), ).toStrictEqual([2, 3, 5, 7]) }) it('should handle always false predicates', () => { expect( pipe( prime, takeLeftWhile(() => false), toReadonlyArray, ), ).toStrictEqual([]) }) }) describe('dropLeft', () => { it('should drop specified elements', () => { const x = pipe(range(0), dropLeft(5), takeLeft(5), toReadonlyArray) expect(x).toHaveLength(5) expect(x).toStrictEqual([5, 6, 7, 8, 9]) }) it('should handle negative numbers', () => { const x = pipe( range(0), dropLeft(-Infinity), takeLeft(5), toReadonlyArray, ) expect(x).toHaveLength(5) expect(x).toStrictEqual([0, 1, 2, 3, 4]) }) }) describe('dropLeftWhile', () => { it('should drop elements according to a predicate', () => { expect( pipe( prime, dropLeftWhile((n) => n < 10), takeLeft(5), toReadonlyArray, ), ).toStrictEqual([11, 13, 17, 19, 23]) }) it('should handle always false predicates', () => { expect( pipe( prime, dropLeftWhile(() => false), takeLeft(5), toReadonlyArray, ), ).toStrictEqual([2, 3, 5, 7, 11]) }) }) describe('zip', () => { it('should zip two lists together', () => { expect( pipe(range(0), zip(prime), takeLeft(5), toReadonlyArray), ).toStrictEqual([ [0, 2], [1, 3], [2, 5], [3, 7], [4, 11], ]) }) it('should cut to the shortest list', () => { expect( pipe(pipe(range(0), takeLeft(5)), zip(prime), toReadonlyArray), ).toStrictEqual([ [0, 2], [1, 3], [2, 5], [3, 7], [4, 11], ]) expect( pipe(range(0), zip(pipe(prime, takeLeft(5))), toReadonlyArray), ).toStrictEqual([ [0, 2], [1, 3], [2, 5], [3, 7], [4, 11], ]) }) }) describe('sieve', () => { it('should allow filtering a list according to its past elements', () => { const atMost = (E: Eq.Eq) => (n: number) => (as: ReadonlyArray, a: A): boolean => pipe( as, RA.reduce( [true, 0] as Readonly<[boolean, number]>, ([all, m], _a) => // eslint-disable-next-line no-nested-ternary all ? E.equals(a, _a) ? ([m + 1 < n, m + 1] as const) : ([all, m] as const) : ([all, m] as const), ), ([all]) => all, ) expect( pipe( fromReadonlyArray([ 'a', 'b', 'a', 'b', 'a', 'b', 'a', 'b', 'a', 'b', 'b', 'c', 'b', 'c', 'b', 'c', 'b', 'c', 'b', 'c', ]), sieve(atMost(St.Eq)(3)), toReadonlyArray, ), ).toStrictEqual(['a', 'b', 'a', 'b', 'a', 'b', 'c', 'c', 'c']) }) }) describe('scanRight', () => { it('should return all steps of a reduction', () => { expect( pipe( range(0), takeLeft(5), scanRight(0, (b, a) => a + b), toReadonlyArray, ), ).toStrictEqual([10, 6, 3, 1, 0, 0]) }) }) describe('spanLeft', () => { it('should split the list when given condition is not met', () => { expect( pipe( prime, takeLeft(5), spanLeft((n) => 0 !== n % 5), ({ init, rest }) => ({ init: toReadonlyArray(init), rest: toReadonlyArray(rest), }), ), ).toStrictEqual({ init: [2, 3], rest: [5, 7, 11] }) }) }) describe('uniq', () => { it('should remove repeated elements', () => { expect( pipe( fromReadonlyArray([ 'a', 'b', 'a', 'b', 'a', 'b', 'a', 'b', 'a', 'b', 'b', 'c', 'b', 'c', 'b', 'c', 'b', 'c', 'b', 'c', ]), uniq(St.Eq), toReadonlyArray, ), ).toStrictEqual(['a', 'b', 'c']) }) }) describe('reverse', () => { it('should return the inverted list', () => { expect(pipe(prime, takeLeft(5), reverse, toReadonlyArray)).toStrictEqual([ 11, 7, 5, 3, 2, ]) }) }) describe('rights', () => { it('should extract Right values', () => { expect( pipe( fromReadonlyArray([ Ei.right(0), Ei.left(1), Ei.right(2), Ei.left(3), Ei.right(4), ]), rights, toReadonlyArray, ), ).toStrictEqual([0, 2, 4]) }) }) describe('lefts', () => { it('should extract Left values', () => { expect( pipe( fromReadonlyArray([ Ei.right(0), Ei.left(1), Ei.right(2), Ei.left(3), Ei.right(4), ]), lefts, toReadonlyArray, ), ).toStrictEqual([1, 3]) }) }) describe('intersperse', () => { it('should insert given element between each pair of list elements', () => { expect( pipe(prime, intersperse(42), takeLeft(5), toReadonlyArray), ).toStrictEqual([2, 42, 3, 42, 5]) }) it('should handle empty lists', () => { expect(pipe(prime, takeLeft(0), toReadonlyArray)).toStrictEqual([]) }) }) describe('rotate', () => { it('should rotate the list by given steps', () => { expect( pipe(prime, takeLeft(5), rotate(2), toReadonlyArray), ).toStrictEqual([7, 11, 2, 3, 5]) }) it('should handle negative numbers', () => { expect( pipe(prime, takeLeft(5), rotate(-2), toReadonlyArray), ).toStrictEqual([5, 7, 11, 2, 3]) }) }) describe('chunksOf', () => { it('should split the list into chunks of a given size', () => { expect( pipe( prime, takeLeft(10), chunksOf(3), map(toReadonlyArray), toReadonlyArray, ), ).toStrictEqual([[2, 3, 5], [7, 11, 13], [17, 19, 23], [29]]) }) it('should force the chunk size to be at least 1', () => { expect( pipe( prime, takeLeft(5), chunksOf(-Infinity), map(toReadonlyArray), toReadonlyArray, ), ).toStrictEqual([[2], [3], [5], [7], [11]]) }) it('should handle empty lists', () => { expect( pipe( prime, takeLeft(0), chunksOf(3), map(toReadonlyArray), toReadonlyArray, ), ).toStrictEqual([]) }) }) describe('matchLeft', () => { it('should handle empty lists', () => { expect( pipe( getMonoid().empty, matchLeft( () => 'nil', (a) => `cons(${a})`, ), ), ).toBe('nil') }) it('should handle non-empty lists', () => { expect( pipe( range(0), matchLeft( () => 'nil', (a) => `cons(${a})`, ), ), ).toBe('cons(0)') }) it('should handle lists of one element', () => { expect( pipe( range(0), takeLeft(1), matchLeft( () => 'nil', (head, tail) => `cons(${head}), ${pipe( tail, matchLeft( () => 'nil', (a) => `cons(${a})`, ), )}`, ), ), ).toBe('cons(0), nil') }) it('should handle infinite lists', () => { expect( pipe( range(0), matchLeft( () => 'nil', (head, tail) => `cons(${head}), ${pipe( tail, matchLeft( () => 'nil', (a) => `cons(${a})`, ), )}`, ), ), ).toBe('cons(0), cons(1)') }) }) describe('matchRight', () => { it('should handle empty lists', () => { expect( pipe( prime, takeLeft(0), matchRight( () => [], (init, last) => [toReadonlyArray(init), last], ), ), ).toHaveLength(0) }) it('should handle non-empty lists', () => { expect( pipe( prime, takeLeft(5), matchRight( () => [], (init, last) => [toReadonlyArray(init), last], ), ), ).toStrictEqual([[2, 3, 5, 7], 11]) }) it('should handle lists of one element', () => { expect( pipe( prime, takeLeft(1), matchRight( () => [], (init, last) => [toReadonlyArray(init), last], ), ), ).toStrictEqual([[], 2]) }) }) describe('toReadonlyArray', () => { it('should transform a generator into an array', () => { expect( pipe( () => (function* () { yield 42 yield 1138 yield 1337 })(), toReadonlyArray, ), ).toStrictEqual([42, 1138, 1337]) }) }) describe('tail', () => { it('should return all elements but first one', () => { expect( pipe(prime, takeLeft(5), tail, O.map(toReadonlyArray)), ).toStrictEqual(O.some([3, 5, 7, 11])) }) it('should fail with empty lists', () => { expect( pipe(prime, takeLeft(0), tail, O.map(toReadonlyArray)), ).toStrictEqual(O.none) }) }) describe('init', () => { it('should return all elements but last one', () => { expect( pipe(prime, takeLeft(5), init, O.map(toReadonlyArray)), ).toStrictEqual(O.some([2, 3, 5, 7])) }) it('should fail with empty lists', () => { expect( pipe(prime, takeLeft(0), init, O.map(toReadonlyArray)), ).toStrictEqual(O.none) }) }) describe('splitAt', () => { it('should split a list at a given position', () => { const x = pipe(prime, takeLeft(10), splitAt(5)) expect(pipe(x[0], toReadonlyArray)).toStrictEqual([2, 3, 5, 7, 11]) expect(pipe(x[1], toReadonlyArray)).toStrictEqual([13, 17, 19, 23, 29]) }) it('should handle negative indices', () => { const x = pipe(prime, takeLeft(5), splitAt(-Infinity)) expect(pipe(x[0], toReadonlyArray)).toStrictEqual([]) expect(pipe(x[1], toReadonlyArray)).toStrictEqual([2, 3, 5, 7, 11]) }) it('should handle out of scale indices', () => { const x = pipe(prime, takeLeft(5), splitAt(Infinity)) expect(pipe(x[0], toReadonlyArray)).toStrictEqual([2, 3, 5, 7, 11]) expect(pipe(x[1], toReadonlyArray)).toStrictEqual([]) }) }) describe('unzip', () => { it('should restore two zipped lists', () => { expect( pipe(prime, zip(range(0)), takeLeft(5), unzip, ([a, b]) => [ toReadonlyArray(a), toReadonlyArray(b), ]), ).toStrictEqual([ [2, 3, 5, 7, 11], [0, 1, 2, 3, 4], ]) }) }) describe('isEmpty', () => { it('should recognize empty lists', () => { expect(pipe(getMonoid().empty, isEmpty)).toBe(true) }) it('should recognize non-empty lists', () => { expect(pipe(range(0), isEmpty)).toBe(false) }) }) describe('isNonEmpty', () => { it('should recognize empty lists', () => { expect(pipe(getMonoid().empty, isNonEmpty)).toBe(false) }) it('should recognize non-empty lists', () => { expect(pipe(range(0), isNonEmpty)).toBe(true) }) }) describe('size', () => { it('should return list size', () => { expect(pipe(prime, takeLeft(5), size)).toBe(5) }) it('should handle empty lists', () => { expect(pipe(prime, takeLeft(0), size)).toBe(0) }) }) describe('lookup', () => { it('should return None when the index is out of bound', () => { expect(pipe(range(0), takeLeft(5), lookup(5))).toStrictEqual(O.none) }) it('should return Some when the index is in bound', () => { expect(pipe(range(0), takeLeft(5), lookup(4))).toStrictEqual(O.some(4)) }) it('should handle negative numbers', () => { expect(pipe(range(0), lookup(-Infinity))).toStrictEqual(O.none) }) }) describe('head', () => { it('should return None with empty lists', () => { expect(pipe(getMonoid().empty, head)).toStrictEqual(O.none) }) it('should return Some with non-empty lists', () => { expect(pipe(range(0), head)).toStrictEqual(O.some(0)) }) }) describe('findFirstIndex', () => { it('should find first matching element and return its index', () => { expect( pipe( range(0), takeLeft(5), findFirstIndex((i) => 0 !== i % 2), ), ).toStrictEqual(O.some(1)) }) it('should return None when no element is found', () => { expect( pipe( range(0), takeLeft(5), findFirstIndex((i) => i >= 5), ), ).toStrictEqual(O.none) }) }) describe('findLast', () => { it('should return None when no element is found', () => { expect( pipe( range(0), takeLeft(5), findLast((n) => n >= 5), ), ).toStrictEqual(O.none) }) it('should return Some when an element is found', () => { expect( pipe( range(0), takeLeft(5), findLast((n) => n >= 4), ), ).toStrictEqual(O.some(4)) }) }) describe('findLastMap', () => { it('should find last matching element and transform it', () => { expect( pipe( range(0), takeLeft(5), findLastMap((i) => 0 === i % 2 ? O.none : O.some(String.fromCharCode('a'.charCodeAt(0) + i)), ), ), ).toStrictEqual(O.some('d')) }) it('should return None when no element is found', () => { expect( pipe( range(0), takeLeft(5), findLastMap((i) => i < 5 ? O.none : O.some(String.fromCharCode('a'.charCodeAt(0) + i)), ), ), ).toStrictEqual(O.none) }) }) describe('findLastIndex', () => { it('should find last matching element and return its index', () => { expect( pipe( range(0), takeLeft(5), findLastIndex((i) => 0 !== i % 2), ), ).toStrictEqual(O.some(3)) }) it('should return None when no element is found', () => { expect( pipe( range(0), takeLeft(5), findLastIndex((i) => i >= 5), ), ).toStrictEqual(O.none) }) }) describe('elem', () => { it('should return None when the element is not found', () => { expect(pipe(range(0), takeLeft(5), elem(N.Eq)(5))).toStrictEqual(O.none) }) it('should return Some when the element is found', () => { expect(pipe(range(0), elem(N.Eq)(4))).toStrictEqual(O.some(4)) }) }) describe('insertAt', () => { it('should insert an element at a given position', () => { expect( pipe(prime, insertAt(2, 42), O.map(flow(takeLeft(5), toReadonlyArray))), ).toStrictEqual(O.some([2, 3, 42, 5, 7])) }) it('should handle out of bound indices', () => { expect( pipe( prime, takeLeft(5), insertAt(Infinity, 42), O.map(toReadonlyArray), ), ).toStrictEqual(O.none) }) }) describe('updateAt', () => { it('should update an element at a given position', () => { expect( pipe(prime, updateAt(2, 42), O.map(flow(takeLeft(5), toReadonlyArray))), ).toStrictEqual(O.some([2, 3, 42, 7, 11])) }) it('should handle out of bound indices', () => { expect( pipe( prime, takeLeft(5), updateAt(Infinity, 42), O.map(toReadonlyArray), ), ).toStrictEqual(O.none) }) }) describe('deleteAt', () => { it('should delete an element at a given position', () => { expect( pipe(prime, deleteAt(2), O.map(flow(takeLeft(5), toReadonlyArray))), ).toStrictEqual(O.some([2, 3, 7, 11, 13])) }) it('should handle out of bound indices', () => { expect( pipe(prime, takeLeft(5), deleteAt(Infinity), O.map(toReadonlyArray)), ).toStrictEqual(O.none) }) }) describe('Monoid', () => { const { empty, concat } = getMonoid() const a = fromReadonlyArray([0, 1, 2]) const b = fromReadonlyArray([3, 4, 5]) const c = fromReadonlyArray([6, 7, 8]) it('associativity', () => { expect(pipe(concat(a, concat(b, c)), toReadonlyArray)).toStrictEqual( pipe(concat(concat(a, b), c), toReadonlyArray), ) }) it('identity', () => { expect(pipe(concat(empty, empty), toReadonlyArray)).toStrictEqual( pipe(empty, toReadonlyArray), ) expect(pipe(concat(a, empty), toReadonlyArray)).toStrictEqual( pipe(a, toReadonlyArray), ) expect(pipe(concat(empty, a), toReadonlyArray)).toStrictEqual( pipe(a, toReadonlyArray), ) }) it('should concatenate generated values', () => { expect(pipe(concat(a, concat(b, c)), toReadonlyArray)).toStrictEqual([ 0, 1, 2, 3, 4, 5, 6, 7, 8, ]) }) }) describe('Eq', () => { const { equals } = getEq(N.Eq) const fa = fromReadonlyArray([0, 1, 2]) const fb = pipe(range(0), takeLeft(3)) const fc = function* () { yield 0 yield 1 yield 2 } const fd = fromReadonlyArray([3, 4, 5]) it('reflexivity', () => { expect(equals(fa, fa)).toBe(true) }) it('symmetry', () => { expect(equals(fa, fb)).toBe(equals(fb, fa)) expect(equals(fa, fd)).toBe(equals(fd, fa)) }) it('transitivity', () => { expect(equals(fa, fb)).toBe(true) expect(equals(fb, fc)).toBe(true) expect(equals(fa, fc)).toBe(true) expect(equals(fa, fd)).toBe(false) expect(equals(fb, fd)).toBe(false) expect(equals(fc, fd)).toBe(false) }) }) describe('Ord', () => { const { equals, compare } = getOrd(N.Ord) const fa = fromReadonlyArray([0, 1, 2]) const fb = pipe(range(3), takeLeft(3)) const fc = function* () { yield 6 yield 7 yield 8 } const fd = fromReadonlyArray([0, 1, 2]) it('reflexivity', () => { expect(compare(fa, fa)).toBeLessThanOrEqual(0) }) it('antisymmetry', () => { expect(compare(fa, fd)).toBeLessThanOrEqual(0) expect(compare(fd, fa)).toBeLessThanOrEqual(0) expect(equals(fa, fd)).toBe(true) }) it('transitivity', () => { expect(compare(fa, fb)).toBeLessThanOrEqual(0) expect(compare(fb, fc)).toBeLessThanOrEqual(0) expect(compare(fa, fc)).toBeLessThanOrEqual(0) expect(compare(fa, fd)).toBeLessThanOrEqual(0) expect(compare(fb, fd)).toBeGreaterThan(0) expect(compare(fc, fd)).toBeGreaterThan(0) }) }) describe('Functor', () => { const fa = fromReadonlyArray([0, 1, 2]) it('identity', () => { expect( pipe( Functor.map(fa, (a) => a), toReadonlyArray, ), ).toStrictEqual(pipe(fa, toReadonlyArray)) }) it('composition', () => { const ab = (a: number) => a + 1 const bc = (a: number) => a / 2 expect( pipe( Functor.map(fa, (a) => bc(ab(a))), toReadonlyArray, ), ).toStrictEqual( pipe(Functor.map(Functor.map(fa, ab), bc), toReadonlyArray), ) }) }) describe('FunctorWithIndex', () => { const fa = fromReadonlyArray([0, 1, 2]) it('identity', () => { expect( pipe( FunctorWithIndex.mapWithIndex(fa, (_, a) => a), toReadonlyArray, ), ).toStrictEqual(pipe(fa, toReadonlyArray)) }) it('composition', () => { const ab = (a: number) => a + 1 const bc = (a: number) => a / 2 expect( pipe( FunctorWithIndex.mapWithIndex(fa, (_, a) => bc(ab(a))), toReadonlyArray, ), ).toStrictEqual( pipe( FunctorWithIndex.mapWithIndex( FunctorWithIndex.mapWithIndex(fa, (_, a) => ab(a)), (_, b) => bc(b), ), toReadonlyArray, ), ) }) }) describe('Apply', () => { const fa = fromReadonlyArray([0, 1, 2]) const ab = (a: number) => a + 1 const bc = (a: number) => a / 2 it('associative composition', () => { expect( pipe( Apply.ap( Apply.ap( Apply.map( of(bc), (bc) => (ab: (a: number) => number) => (a: number) => bc(ab(a)), ), of(ab), ), fa, ), toReadonlyArray, ), ).toStrictEqual( pipe(Apply.ap(of(bc), Apply.ap(of(ab), fa)), toReadonlyArray), ) }) }) describe('Applicative', () => { const a = 42 const fa = fromReadonlyArray([0, 1, 2]) const ab = (a: number) => a + 1 it('identity', () => { expect( pipe( Applicative.ap( Applicative.of((a: number) => a), fa, ), toReadonlyArray, ), ).toStrictEqual(pipe(fa, toReadonlyArray)) }) it('homomorphism', () => { expect( pipe( Applicative.ap(Applicative.of(ab), Applicative.of(a)), toReadonlyArray, ), ).toStrictEqual(pipe(Applicative.of(ab(a)), toReadonlyArray)) }) it('interchange', () => { expect( pipe( Applicative.ap(Applicative.of(ab), Applicative.of(a)), toReadonlyArray, ), ).toStrictEqual( pipe( Applicative.ap( Applicative.of((ab: (a: number) => number) => ab(a)), Applicative.of(ab), ), toReadonlyArray, ), ) }) }) describe('Chain', () => { const fa = fromReadonlyArray([0, 1, 2]) const afb = (a: number) => of(a + 1) const bfc = (a: number) => of(a / 2) it('associativity', () => { expect( pipe(Chain.chain(Chain.chain(fa, afb), bfc), toReadonlyArray), ).toStrictEqual( pipe( Chain.chain(fa, (a) => Chain.chain(afb(a), bfc)), toReadonlyArray, ), ) }) }) describe('Monad', () => { const a = 42 const fa = fromReadonlyArray([0, 1, 2]) const f = (a: number) => of(a + 1) it('left identity', () => { expect(pipe(Monad.chain(Monad.of(a), f), toReadonlyArray)).toStrictEqual( pipe(f(a), toReadonlyArray), ) }) it('right identity', () => { expect(pipe(Monad.chain(fa, Monad.of), toReadonlyArray)).toStrictEqual( pipe(fa, toReadonlyArray), ) }) }) describe('FromIO', () => { describe('fromIO', () => { it('should generate values using an IO', () => { const now = Date.now() const as = pipe( FromIO.fromIO(() => Date.now()), takeLeft(100), toReadonlyArray, ) expect(as[0]).toBeGreaterThanOrEqual(now) expect(as[99]).toBeLessThanOrEqual(Date.now()) }) }) }) describe('Unfoldable', () => { describe('unfold', () => { it('should return a list starting from a given value using a function', () => { const factors = unfold((b: number) => pipe( prime, takeLeftWhile((n) => n <= b), dropLeftWhile((n) => 0 !== b % n), head, O.map((n) => [n, b / n]), ), ) expect(pipe(42, factors, toReadonlyArray)).toStrictEqual([2, 3, 7]) }) }) }) describe('Alt', () => { const fa = fromReadonlyArray([0, 1, 2]) const ga = fromReadonlyArray([3, 4, 5]) const ha = fromReadonlyArray([6, 7, 8]) it('associativity', () => { expect( pipe( Alt.alt( Alt.alt(fa, () => ga), () => ha, ), toReadonlyArray, ), ).toStrictEqual( pipe( Alt.alt(fa, () => Alt.alt(ga, () => ha)), toReadonlyArray, ), ) }) it('distributivity', () => { const ab = (a: number) => a + 1 expect( pipe( Alt.map( Alt.alt(fa, () => ga), ab, ), toReadonlyArray, ), ).toStrictEqual( pipe( Alt.alt(Alt.map(fa, ab), () => Alt.map(ga, ab)), toReadonlyArray, ), ) }) }) describe('Alternative', () => { const fa = fromReadonlyArray([0, 1, 2]) it('left identity', () => { expect( pipe( Alternative.alt(Alternative.zero(), () => fa), toReadonlyArray, ), ).toStrictEqual(pipe(fa, toReadonlyArray)) }) it('right identity', () => { expect( pipe(Alternative.alt(fa, Alternative.zero), toReadonlyArray), ).toStrictEqual(pipe(fa, toReadonlyArray)) }) it('annihilation', () => { const f = (a: number) => a + 1 expect( pipe(Alternative.map(Alternative.zero(), f), toReadonlyArray), ).toStrictEqual(pipe(Alternative.zero(), toReadonlyArray)) }) it('distributivity', () => { const fab = of((a: number) => a + 1) const gab = of((a: number) => a / 2) expect( pipe( Alternative.ap( Alternative.alt(fab, () => gab), fa, ), toReadonlyArray, ), ).toStrictEqual( pipe( Alternative.alt(Alternative.ap(fab, fa), () => Alternative.ap(gab, fa), ), toReadonlyArray, ), ) }) it('annihilation', () => { expect( pipe(Alternative.ap(Alternative.zero(), fa), toReadonlyArray), ).toStrictEqual(pipe(Alternative.zero(), toReadonlyArray)) }) }) describe('Extend', () => { describe('duplicate', () => { it('should duplicate the list on each element', () => { expect( pipe( prime, takeLeft(5), duplicate, map(toReadonlyArray), toReadonlyArray, ), ).toStrictEqual([ [2, 3, 5, 7, 11], [3, 5, 7, 11], [5, 7, 11], [7, 11], [11], ]) }) }) }) describe('Compactable', () => { describe('compact', () => { it('should remove None elements', () => { expect( pipe( fromReadonlyArray([ O.none, O.some(0), O.none, O.some(1), O.none, O.some(2), ]), Compactable.compact, toReadonlyArray, ), ).toStrictEqual([0, 1, 2]) }) }) describe('separate', () => { it('should split Left and Right elements', () => { expect( pipe( fromReadonlyArray([ Ei.left('a'), Ei.right(0), Ei.left('b'), Ei.right(1), Ei.left('c'), Ei.right(2), ]), Compactable.separate, (as) => Se.separated(toReadonlyArray(as.left), toReadonlyArray(as.right)), ), ).toStrictEqual(Se.separated(['a', 'b', 'c'], [0, 1, 2])) }) }) }) describe('Filterable', () => { describe('filter', () => { it('should filter elements', () => { expect( pipe( range(0), takeLeft(10), filter((n) => 0 !== n % 2), toReadonlyArray, ), ).toStrictEqual([1, 3, 5, 7, 9]) }) }) describe('filterMap', () => { it('should filter and transform elements', () => { expect( pipe( range(0), takeLeft(10), filterMap((n) => (0 !== n % 2 ? O.some(2 * n) : O.none)), toReadonlyArray, ), ).toStrictEqual([2, 6, 10, 14, 18]) }) }) describe('partition', () => { it('should split elements', () => { expect( pipe( range(0), takeLeft(10), partition((n) => 0 !== n % 2), (as) => Se.separated(toReadonlyArray(as.left), toReadonlyArray(as.right)), ), ).toStrictEqual(Se.separated([0, 2, 4, 6, 8], [1, 3, 5, 7, 9])) }) }) describe('partitionMap', () => { it('should split and transform elements', () => { expect( pipe( range(0), takeLeft(10), partitionMap((n) => 0 !== n % 2 ? Ei.right(2 * n) : Ei.left(-2 * n), ), (as) => Se.separated(toReadonlyArray(as.left), toReadonlyArray(as.right)), ), ).toStrictEqual( Se.separated([-0, -4, -8, -12, -16], [2, 6, 10, 14, 18]), ) }) }) }) describe('FilterableWithIndex', () => { describe('filterWithIndex', () => { it('should filter elements using their index', () => { expect( pipe( range(0), takeLeft(10), filterWithIndex((i, n) => i < 5 && 0 !== n % 2), toReadonlyArray, ), ).toStrictEqual([1, 3]) }) }) describe('filterMapWithIndex', () => { it('should filter and transform elements using their index', () => { expect( pipe( range(0), takeLeft(10), filterMapWithIndex((i, n) => i < 5 && 0 !== n % 2 ? O.some(2 * n) : O.none, ), toReadonlyArray, ), ).toStrictEqual([2, 6]) }) }) describe('partitionWithIndex', () => { it('should split elements using their index', () => { expect( pipe( range(0), takeLeft(10), partitionWithIndex((i, n) => i < 5 && 0 !== n % 2), (as) => Se.separated(toReadonlyArray(as.left), toReadonlyArray(as.right)), ), ).toStrictEqual(Se.separated([0, 2, 4, 5, 6, 7, 8, 9], [1, 3])) }) }) describe('partitionMapWithIndex', () => { it('should split and transform elements using their index', () => { expect( pipe( range(0), takeLeft(10), partitionMapWithIndex((i, n) => i < 5 && 0 !== n % 2 ? Ei.right(2 * n) : Ei.left(-2 * n), ), (as) => Se.separated(toReadonlyArray(as.left), toReadonlyArray(as.right)), ), ).toStrictEqual( Se.separated([-0, -4, -8, -10, -12, -14, -16, -18], [2, 6]), ) }) }) }) describe('FoldableWithIndex', () => { describe('foldMapWithIndex', () => { it('should map elements using their index and fold the resulting list', () => { expect( pipe( prime, takeLeft(5), foldMapWithIndex(N.MonoidSum)((i, a) => i * a), ), ).toBe(78) }) }) }) describe('Traversable', () => { describe('sequence', () => { it('should accumulate the results of the effects contained in a list', () => { expect( pipe( prime, map(O.some), takeLeft(5), sequence(O.Applicative), O.map(toReadonlyArray), ), ).toStrictEqual(O.some([2, 3, 5, 7, 11])) expect( pipe( prime, map(O.some), takeLeft(5), append>(O.none), sequence(O.Applicative), O.map(toReadonlyArray), ), ).toStrictEqual(O.none) }) }) }) describe('Witherable', () => { describe('wilt', () => { it('should split a list applying an effect', () => { expect( pipe( range(0), takeLeft(10), wilt(O.Applicative)((n) => n >= 10 ? O.none : O.some(0 === n % 3 ? Ei.right(n) : Ei.left(n)), ), O.map(Se.bimap(toReadonlyArray, toReadonlyArray)), ), ).toStrictEqual(O.some(Se.separated([1, 2, 4, 5, 7, 8], [0, 3, 6, 9]))) expect( pipe( range(0), takeLeft(10), wilt(O.Applicative)((n) => 0 === n % 2 ? O.none : O.some(0 === n % 3 ? Ei.right(n) : Ei.left(n)), ), O.map(Se.bimap(toReadonlyArray, toReadonlyArray)), ), ).toStrictEqual(O.none) }) }) describe('wither', () => { it('should filter a list applying an effect', () => { expect( pipe( range(0), takeLeft(10), wither(O.Applicative)((n) => O.some(0 === n % 2 ? O.none : O.some(n)), ), O.map(toReadonlyArray), ), ).toStrictEqual(O.some([1, 3, 5, 7, 9])) expect( pipe( range(0), takeLeft(10), wither(O.Applicative)((n) => 0 === n % 2 ? O.none : O.some(O.some(n)), ), ), ).toStrictEqual(O.none) }) }) }) })