/** * @typed/fp/Fx is a generator-based do-notation for single-shot data types. * * @since 0.13.0 */ import { Applicative2 } from 'fp-ts/Applicative' import { Apply2 } from 'fp-ts/Apply' import { ap as ap_ } from 'fp-ts/Chain' import { ChainRec2 } from 'fp-ts/ChainRec' import { Either, isLeft } from 'fp-ts/Either' import { FromIO2 } from 'fp-ts/FromIO' import { Functor2 } from 'fp-ts/Functor' import { IO } from 'fp-ts/IO' import { Monad2 } from 'fp-ts/Monad' import { Pointed2 } from 'fp-ts/Pointed' import { A } from 'ts-toolbelt' /** * Fx is a generator-based abstraction for do-notation for any single-shot * effect. Due to the mutable nature of generators however, we cannot support this syntax * for multi-shot effects like reactive Streams/Observables. Most of the effects you * likely use are single-shot like Option/Either/Task. * * An Fx is a set of Effects which are being `yield`ed from the Generator. * This can be a powerful way to construct algorithms separate from their interpretation. * * Fx's Result parameter is the secret to getting type-safety by using yield* when running an Fx. * * @category Model * @since 0.13.0 */ export interface Fx { readonly [Symbol.iterator]: () => Generator } /** * Extract the effects being performed within an Fx * @category Type-level * @since 0.13.0 */ export type GetEffects = A extends Fx ? IsNever extends false ? R : unknown : unknown type IsNever = A.Equals<[never], [A]> extends 1 ? true : false /** * Extract the result being performed within an Fx * @category Type-level * @since 0.13.0 */ export type GetResult = A extends Fx ? R : never /** * Extract the values being returned to the internal Fx * @category Type-level * @since 0.13.0 */ export type GetNext = A extends Fx ? R : never /** * Extract the values being returned to the internal Fx * @category Combinator * @since 0.13.0 */ export function doFx>( generatorFn: () => G, ): Fx, GetResult, GetNext> { return { [Symbol.iterator]: generatorFn, } } /** * An Fx which has no Effects or they have all been accounted for. * @category Model * @since 0.13.0 */ export interface Pure extends Fx {} /** * @category Constructor * @since 0.13.0 */ export const pure = (value: A): Pure => // eslint-disable-next-line require-yield doFx(function* () { return value }) /** * @category Constructor * @since 0.13.0 */ export const fromIO = (io: IO): Pure => // eslint-disable-next-line require-yield doFx(function* () { return io() }) /** * @category URI * @since 0.13.0 */ export const URI = '@typed/fp/Fx' /** * @category URI * @since 0.13.0 */ export type URI = typeof URI declare module 'fp-ts/HKT' { export interface URItoKind2 { [URI]: Fx } } /** * @category Combinator * @since 0.13.0 */ export const map = (f: (value: A) => B) => (fa: Fx): Fx => doFx(function* () { return f(yield* fa) }) /** * @category Instance * @since 0.13.0 */ export const Functor: Functor2 = { URI, map, } /** * @category Constructor * @since 0.13.0 */ export const of = pure /** * @category Instance * @since 0.13.0 */ export const Pointed: Pointed2 = { of, } /** * @category Combinator * @since 0.13.0 */ export const chain = (f: (value: A) => Fx) => (fa: Fx): Fx => doFx(function* () { const a = yield* fa return yield* f(a) }) /** * @category Instance * @since 0.13.0 */ export const Monad: Monad2 = { ...Pointed, ...Functor, chain, } /** * @category Combinator * @since 0.13.0 */ export const chainRec = (f: (value: A) => Fx>) => (value: A): Fx => doFx(function* () { let either = yield* f(value) while (isLeft(either)) { either = yield* f(either.left) } return either.right }) /** * @category Instance * @since 0.13.0 */ export const ChainRec: ChainRec2 = { URI, chainRec, } /** * @category Combinator * @since 0.13.0 */ export const ap = ap_(Monad) as ( fa: Fx, ) => (fab: Fx B, unknown>) => Fx /** * @category Instance * @since 0.13.0 */ export const Apply: Apply2 = { ...Functor, ap } /** * @category Instance * @since 0.13.0 */ export const Applicative: Applicative2 = { ...Pointed, ...Apply } /** * @category Instance * @since 0.13.0 */ export const FromIO: FromIO2 = { URI, fromIO }