// ets_tracing: off /* eslint-disable prefer-const */ import "../Operator/index.js" import * as Tp from "../Collections/Immutable/Tuple/index.js" import { _A, _U } from "../Effect/commons.js" import { Stack } from "../Stack/index.js" import type { HasUnify } from "../Utils/index.js" import { unifyIndex } from "../Utils/index.js" /** * `IO[A]` is a purely functional description of a computation. * * Note: while for general cases the `Sync` data type is preferrable, * this data type is designed for speed and low allocations, * it is internally used to suspend recursive procedures but can be * useful whenever you need a fast sync computation that cannot fail * and that doesn't require any environment. */ export type IO = Succeed | FlatMap | Suspend export const IoURI = Symbol() export type IoURI = typeof IoURI declare module "../Utils" { export interface UnifiableIndexed { [IoURI]: [X] extends [IO] ? IO : never } } interface Base extends HasUnify {} abstract class Base { readonly [unifyIndex]: IoURI; readonly [_U]!: "IO"; readonly [_A]!: () => A } class Succeed extends Base { readonly _iotag = "Succeed" constructor(readonly a: A) { super() } } class Suspend extends Base { readonly _iotag = "Suspend" constructor(readonly f: () => IO) { super() } } class FlatMap extends Base { readonly _iotag = "FlatMap" constructor(readonly value: IO, readonly cont: (a: A) => IO) { super() } } /** * Runs this computation */ export function run(self: IO): A { let stack: Stack<(e: any) => IO> | undefined = undefined let a = undefined let curIO = self as IO | undefined while (curIO != null) { switch (curIO._iotag) { case "FlatMap": { switch (curIO.value._iotag) { case "Succeed": { curIO = curIO.cont(curIO.value.a) break } default: { stack = new Stack(curIO.cont, stack) curIO = curIO.value } } break } case "Suspend": { curIO = curIO.f() break } case "Succeed": { a = curIO.a if (stack) { curIO = stack.value(a) stack = stack.previous } else { curIO = undefined } break } } } return a } /** * Extends this computation with another computation that depends on the * result of this computation by running the first computation, using its * result to generate a second computation, and running that computation. * * @ets_data_first chain_ */ export function chain(f: (a: A) => IO) { return (self: IO): IO => new FlatMap(self, f) } /** * Extends this computation with another computation that depends on the * result of this computation by running the first computation, using its * result to generate a second computation, and running that computation. */ export function chain_(self: IO, f: (a: A) => IO): IO { return new FlatMap(self, f) } /** * Returns a computation that effectfully "peeks" at the success of this one. * * @ets_data_first tap_ */ export function tap(f: (a: A) => IO) { return (self: IO): IO => tap_(self, f) } /** * Returns a computation that effectfully "peeks" at the success of this one. */ export function tap_(self: IO, f: (a: A) => IO): IO { return chain_(self, (a) => map_(f(a), () => a)) } /** * Constructs a computation that always succeeds with the specified value. */ export function succeed(a: A): IO { return new Succeed(a) } /** * Extends this computation with another computation that depends on the * result of this computation by running the first computation, using its * result to generate a second computation, and running that computation. */ export function map_(self: IO, f: (a: A) => B) { return chain_(self, (a) => succeed(f(a))) } /** * Extends this computation with another computation that depends on the * result of this computation by running the first computation, using its * result to generate a second computation, and running that computation. * * @ets_data_first map_ */ export function map(f: (a: A) => B) { return (self: IO) => map_(self, f) } /** * Constructs a computation that always returns the `Unit` value. */ export const unit: IO = new Succeed(undefined) /** * Combines this computation with the specified computation combining the * results of both using the specified function. * * @ets_data_first zipWith_ */ export function zipWith(that: IO, f: (a: A, b: B) => C) { return (self: IO): IO => zipWith_(self, that, f) } /** * Combines this computation with the specified computation combining the * results of both using the specified function. */ export function zipWith_(self: IO, that: IO, f: (a: A, b: B) => C) { return chain_(self, (a) => map_(that, (b) => f(a, b))) } /** * Combines this computation with the specified computation, combining the * results of both into a tuple. * * @ets_data_first zip_ */ export function zip(that: IO) { return (self: IO) => zip_(self, that) } /** * Combines this computation with the specified computation combining the * results of both into a tuple. */ export function zip_(self: IO, that: IO) { return zipWith_(self, that, Tp.tuple) } /** * Suspend a computation, useful in recursion */ export function suspend(f: () => IO): IO { return new Suspend(f) } /** * Lift a sync (non failable) computation */ export function succeedWith(f: () => A) { return suspend(() => succeed(f())) } export class GenIO { readonly _A!: () => A constructor(readonly effect: IO) {} *[Symbol.iterator](): Generator, A, any> { return yield this } } function adapter(_: IO): GenIO { return new GenIO(_) } function run_, AEff>( state: IteratorYieldResult | IteratorReturnResult, iterator: Generator ): IO { if (state.done) { return succeed(state.value) } return chain_(state.value["effect"], (val) => { const next = iterator.next(val) return run_(next, iterator) }) } /** * Generator */ export function gen, AEff>( f: (i: { (_: IO): GenIO }) => Generator ): IO { return suspend(() => { const iterator = f(adapter) const state = iterator.next() return run_(state, iterator) }) }