// 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)
})
}