import * as Appli from 'fp-ts/Applicative'
import * as _Apply from 'fp-ts/Apply'
import * as Ch from 'fp-ts/Chain'
import * as Co from 'fp-ts/Compactable'
import * as Ei from 'fp-ts/Either'
import * as Eq from 'fp-ts/Eq'
import * as Fi from 'fp-ts/Filterable'
import * as FiWI from 'fp-ts/FilterableWithIndex'
import * as FIO from 'fp-ts/FromIO'
import { constFalse, constTrue, flip, flow, Lazy, pipe } from 'fp-ts/function'
import * as Fu from 'fp-ts/Functor'
import * as FuWI from 'fp-ts/FunctorWithIndex'
import * as IO from 'fp-ts/IO'
import * as Mona from 'fp-ts/Monad'
import * as MIO from 'fp-ts/MonadIO'
import * as Mono from 'fp-ts/Monoid'
import * as O from 'fp-ts/Option'
import * as P from 'fp-ts/Pointed'
import * as Pr from 'fp-ts/Predicate'
import * as RA from 'fp-ts/ReadonlyArray'
import * as Re from 'fp-ts/Refinement'
import * as S from 'fp-ts/Separated'
import * as T from 'fp-ts/Task'
import { curry } from './function'
import * as $Y from './Yield'
export const URI = 'AsyncYield'
export type URI = typeof URI
declare module 'fp-ts/HKT' {
interface URItoKind {
[URI]: AsyncYield
}
}
export type AsyncYield = IO.IO>
export const getMonoid = (): Mono.Monoid> => ({
empty: fromReadonlyArray([]),
concat: (x, y) =>
async function* () {
yield* x()
yield* y()
},
})
export const fromIOGenerator = (as: $Y.Yield): AsyncYield =>
async function* () {
for (const a of as()) {
yield a
}
}
export const Functor: Fu.Functor1 = {
URI,
map: (fa, f) =>
async function* () {
for await (const a of fa()) {
yield f(a)
}
},
}
export const map = curry(flip(Functor.map))
export const flap = Fu.flap(Functor)
export const bindTo = Fu.bindTo(Functor)
export const FunctorWithIndex: FuWI.FunctorWithIndex1 = {
...Functor,
mapWithIndex: (fa, f) =>
Functor.map(pipe(fa, zip(range(0))), ([a, i]) => f(i, a)),
}
export const Pointed: P.Pointed1 = {
URI,
of: (a) => fromIOGenerator($Y.of(a)),
}
export const of = Pointed.of
export const Do = Pointed.of({})
export const ApplyPar: _Apply.Apply1 = {
...Functor,
ap: (fab, fa) =>
async function* () {
const _fab = fab()
const _fa = fa()
const abs = []
const as = []
// eslint-disable-next-line no-constant-condition
while (true) {
const [ab, a] = await Promise.all([_fab.next(), _fa.next()])
if (ab.done && a.done) {
break
}
if (!ab.done) {
abs.push(ab.value)
}
if (!a.done) {
as.push(a.value)
}
}
for (const ab of abs) {
for (const a of as) {
yield ab(a)
}
}
},
}
/**
*
*/
export const ApplySeq: _Apply.Apply1 = {
...Functor,
ap: (fab, fa) =>
async function* () {
const abs = []
for await (const ab of fab()) {
abs.push(ab)
}
const as = []
for await (const a of fa()) {
as.push(a)
}
for (const ab of abs) {
for (const a of as) {
yield ab(a)
}
}
},
}
export const ap = curry(flip(ApplyPar.ap))
export const apFirst = _Apply.apFirst(ApplyPar)
export const apSecond = _Apply.apSecond(ApplyPar)
export const apS = _Apply.apS(ApplyPar)
export const ApplicativePar: Appli.Applicative1 = {
...Pointed,
...ApplyPar,
}
export const ApplicativeSeq: Appli.Applicative1 = {
...Pointed,
...ApplySeq,
}
export const Chain: Ch.Chain1 = {
...ApplySeq,
chain: (fa, f) => {
return flatten(Functor.map(fa, f))
},
}
export const chain = curry(flip(Chain.chain))
export const chainFirst = Ch.chainFirst(Chain)
export const bind = Ch.bind(Chain)
export const Monad: Mona.Monad1 = { ...ApplicativeSeq, ...Chain }
export const FromIO: FIO.FromIO1 = {
URI,
fromIO: (fa) => () => Pointed.of(fa())(),
}
export const fromIO = FromIO.fromIO
export const fromIOK = FIO.fromIOK(FromIO)
export const chainIOK = FIO.chainIOK(FromIO, Chain)
export const chainFirstIOK = FIO.chainFirstIOK(FromIO, Chain)
export const fromIOReadonlyArray = (
fa: IO.IO>,
): AsyncYield => pipe(fa, fromIO, chain(fromReadonlyArray))
export const fromIOReadonlyArrayK = , B>(
f: (...a: A) => IO.IO>,
) => flow(f, fromIOReadonlyArray)
export const chainIOReadonlyArrayK = (
f: (a: A) => IO.IO>,
) => chain(fromIOReadonlyArrayK(f))
export const MonadIO: MIO.MonadIO1 = { ...Monad, ...FromIO }
function _filter(
fa: AsyncYield,
refinement: Re.Refinement,
): AsyncYield
function _filter(
fa: AsyncYield,
predicate: Pr.Predicate,
): AsyncYield
function _filter(fa: AsyncYield, predicate: Pr.Predicate) {
return async function* () {
for await (const a of fa()) {
if (!predicate(a)) {
continue
}
yield a
}
}
}
export const Compactable: Co.Compactable1 = {
URI,
compact: (fa) => Functor.map(Filterable.filter(fa, O.isSome), (a) => a.value),
separate: (fa) =>
S.separated(
Functor.map(_filter(fa, Ei.isLeft), (a) => a.left),
Functor.map(_filter(fa, Ei.isRight), (a) => a.right),
),
}
export const compact = Compactable.compact
export const separate = Compactable.separate
function _partition(
fa: AsyncYield,
refinement: Re.Refinement,
): S.Separated, AsyncYield>
function _partition(
fa: AsyncYield,
predicate: Pr.Predicate,
): S.Separated, AsyncYield>
function _partition(fa: AsyncYield, predicate: Pr.Predicate) {
return S.separated(_filter(fa, Pr.not(predicate)), _filter(fa, predicate))
}
export const Filterable: Fi.Filterable1 = {
...Functor,
...Compactable,
filter: _filter,
filterMap: (fa, f) => Compactable.compact(Functor.map(fa, f)),
partition: _partition,
partitionMap: (fa, f) => Compactable.separate(Functor.map(fa, f)),
}
export function filter(
refinement: Re.Refinement,
): (fa: AsyncYield) => AsyncYield
export function filter(
predicate: Pr.Predicate,
): (fa: AsyncYield) => AsyncYield
export function filter(predicate: Pr.Predicate) {
return (fa: AsyncYield) => Filterable.filter(fa, predicate)
}
export const filterMap = curry(flip(Filterable.filterMap))
export function partition(
refinement: Re.Refinement,
): (fa: AsyncYield) => S.Separated, AsyncYield>
export function partition(
predicate: Pr.Predicate,
): (fa: AsyncYield) => S.Separated, AsyncYield>
export function partition(predicate: Pr.Predicate) {
return (fa: AsyncYield) => Filterable.partition(fa, predicate)
}
export const partitionMap = curry(flip(Filterable.partitionMap))
function _filterWithIndex(
fa: AsyncYield,
refinementWithIndex: (i: number, a: A) => a is B,
): AsyncYield
function _filterWithIndex(
fa: AsyncYield,
predicateWithIndex: (i: number, a: A) => boolean,
): AsyncYield
function _filterWithIndex(
fa: AsyncYield,
predicateWithIndex: (i: number, a: A) => boolean,
) {
return Compactable.compact(
FunctorWithIndex.mapWithIndex(fa, (i, a) =>
predicateWithIndex(i, a) ? O.some(a) : O.none,
),
)
}
function _partitionWithIndex(
fa: AsyncYield,
refinementWithIndex: (i: number, a: A) => a is B,
): S.Separated, AsyncYield>
function _partitionWithIndex(
fa: AsyncYield,
predicateWithIndex: (i: number, a: A) => boolean,
): S.Separated, AsyncYield>
function _partitionWithIndex(
fa: AsyncYield,
predicateWithIndex: (i: number, a: A) => boolean,
) {
return Compactable.separate(
FunctorWithIndex.mapWithIndex(fa, (i, a) =>
predicateWithIndex(i, a) ? Ei.right(a) : Ei.left(a),
),
)
}
export const FilterableWithIndex: FiWI.FilterableWithIndex1 = {
...FunctorWithIndex,
...Filterable,
filterWithIndex: _filterWithIndex,
filterMapWithIndex: (fa, f) =>
Compactable.compact(FunctorWithIndex.mapWithIndex(fa, f)),
partitionWithIndex: _partitionWithIndex,
partitionMapWithIndex: (fa, f) =>
Compactable.separate(FunctorWithIndex.mapWithIndex(fa, f)),
}
export function filterWithIndex(
refinementWithIndex: (i: number, a: A) => a is B,
): (fa: AsyncYield) => AsyncYield
export function filterWithIndex(
predicateWithIndex: (i: number, a: A) => boolean,
): (fa: AsyncYield) => AsyncYield
export function filterWithIndex(
predicateWithIndex: (i: number, a: A) => boolean,
) {
return (fa: AsyncYield) =>
FilterableWithIndex.filterWithIndex(fa, predicateWithIndex)
}
export const filterMapWithIndex = curry(
flip(FilterableWithIndex.filterMapWithIndex),
)
export function partitionWithIndex(
refinementWithIndex: (i: number, a: A) => a is B,
): (fa: AsyncYield) => S.Separated, AsyncYield>
export function partitionWithIndex(
predicateWithIndex: (i: number, a: A) => boolean,
): (fa: AsyncYield) => S.Separated, AsyncYield>
export function partitionWithIndex(
predicateWithIndex: (i: number, a: A) => boolean,
) {
return (fa: AsyncYield) =>
FilterableWithIndex.partitionWithIndex(fa, predicateWithIndex)
}
export const partitionMapWithIndex = curry(
flip(FilterableWithIndex.partitionMapWithIndex),
)
export const range = flow($Y.range, fromIOGenerator)
export const replicate = flow($Y.replicate, fromIOGenerator)
export const fromReadonlyArray = (x: ReadonlyArray) => {
return flow($Y.fromReadonlyArray, fromIOGenerator)(x)
}
export const sieve =
(f: (init: ReadonlyArray, a: A) => boolean) =>
(as: AsyncYield): AsyncYield =>
async function* () {
const init: Array = []
for await (const a of as()) {
if (!f(init, a)) {
continue
}
init.push(a)
yield a
}
}
export const prime: AsyncYield = pipe(
range(2),
sieve((init, a) =>
pipe(
init,
RA.every((_a) => 0 !== a % _a),
),
),
)
export const exp: AsyncYield = pipe(
range(0),
map((n) => Math.exp(n)),
)
export const fibonacci: AsyncYield = async function* () {
for (let as = [1, 0] as [number, number]; true; as = [as[1], as[0] + as[1]]) {
yield as[1]
}
}
export const flatten = (as: AsyncYield>): AsyncYield =>
async function* () {
for await (const a of as()) {
yield* a()
}
}
export const take =
(n: number) =>
(as: AsyncYield): AsyncYield =>
async function* () {
for await (const [a, i] of pipe(as, zip(range(0)))()) {
if (i >= n) {
break
}
yield a
}
}
export const drop =
(n: number) =>
(as: AsyncYield): AsyncYield =>
async function* () {
for await (const [a, i] of pipe(as, zip(range(0)))()) {
if (i < n) {
continue
}
yield a
}
}
export const zip =
(bs: AsyncYield) =>
(as: AsyncYield): AsyncYield> =>
async function* () {
const _bs = bs()
for await (const a of as()) {
const b = await _bs.next()
if (b.done) {
break
}
yield [a, b.value] as const
}
}
export const uniq = (E: Eq.Eq) =>
sieve((init, a) => !pipe(init, RA.elem(E)(a)))
export const match =
(onEmpty: Lazy, onNonEmpty: (head: A, tail: AsyncYield) => B) =>
(as: AsyncYield): T.Task =>
pipe(
as().next(),
(a) => () =>
a.then((_a) =>
_a.done ? onEmpty() : onNonEmpty(_a.value, pipe(as, drop(1))),
),
)
export const toTask =
(as: AsyncYield): T.Task> =>
async () => {
const _as: Array = []
for await (const a of as()) {
_as.push(a)
}
return _as
}
export const isEmpty = match(constTrue, constFalse)
export const isNonEmpty = flow(
isEmpty,
T.map((a) => !a),
)
export const lookup =
(i: number) =>
(as: AsyncYield): T.Task> =>
i < 0
? T.of(O.none)
: pipe(
as,
drop(i),
match(
() => O.none,
(a) => O.some(a),
),
)
export const head = (as: AsyncYield): T.Task> =>
pipe(as, lookup(0))
export function find(
refinement: Re.Refinement,
): (as: AsyncYield) => T.Task>
export function find(
predicate: Pr.Predicate,
): (as: AsyncYield) => T.Task>
export function find(predicate: Pr.Predicate) {
return (as: AsyncYield) => async () => {
for await (const a of as()) {
if (predicate(a)) {
return O.some(a)
}
}
return O.none
}
}
export const elem =
(Eq: Eq.Eq) =>
(a: A) =>
find((_a: A) => Eq.equals(a, _a))