UNPKG

@effect-ts/system

Version:

Effect-TS is a zero dependency set of libraries to write highly productive, purely functional TypeScript at scale.

574 lines (508 loc) 19.8 kB
// ets_tracing: off import "../../Operator/index.mjs"; import * as Chunk from "../../Collections/Immutable/Chunk/index.mjs"; import * as Map from "../../Collections/Immutable/Map/index.mjs"; import * as Tp from "../../Collections/Immutable/Tuple/index.mjs"; import * as Ex from "../../Exit/index.mjs"; import { not, pipe, tuple } from "../../Function/index.mjs"; import * as O from "../../Option/index.mjs"; import * as RM from "../../RefM/index.mjs"; import * as T from "../_internal/effect.mjs"; import * as M from "../_internal/managed.mjs"; import * as R from "../_internal/ref.mjs"; // Contract notes for transducers: // - When a None is received, the transducer must flush all of its internal state // and remain empty until subsequent Some(Chunk) values. // // Stated differently, after a first push(None), all subsequent push(None) must // result in empty []. export class Transducer { constructor(push) { this.push = push; } } /** * Contract notes for transducers: * - When a None is received, the transducer must flush all of its internal state * and remain empty until subsequent Some(Chunk) values. * * Stated differently, after a first push(None), all subsequent push(None) must * result in empty []. */ export const transducer = push => new Transducer(push); /** * Compose this transducer with another transducer, resulting in a composite transducer. */ export const andThen = that => self => transducer(M.zipWith_(self.push, that.push, (pushLeft, pushRight) => O.fold(() => T.chain_(pushLeft(O.none), cl => Chunk.isEmpty(cl) ? pushRight(O.none) : T.zipWith_(pushRight(O.some(cl)), pushRight(O.none), Chunk.concat_)), inputs => T.chain_(pushLeft(O.some(inputs)), cl => pushRight(O.some(cl)))))); /** * Transforms the outputs of this transducer. */ export function map_(fa, f) { return new Transducer(M.map_(fa.push, push => input => T.map_(push(input), Chunk.map(f)))); } /** * Transforms the outputs of this transducer. */ export function map(f) { return fa => map_(fa, f); } /** * Transforms the chunks emitted by this transducer. */ export function mapChunks_(fa, f) { return new Transducer(M.map_(fa.push, push => input => T.map_(push(input), f))); } /** * Transforms the chunks emitted by this transducer. */ export function mapChunks(f) { return fa => mapChunks_(fa, f); } /** * Effectfully transforms the chunks emitted by this transducer. */ export function mapChunksM_(fa, f) { return new Transducer(M.map_(fa.push, push => input => T.chain_(push(input), f))); } /** * Effectfully transforms the chunks emitted by this transducer. */ export function mapChunksM(f) { return fa => mapChunksM_(fa, f); } /** * Effectually transforms the outputs of this transducer */ export function mapM_(fa, f) { return new Transducer(M.map_(fa.push, push => input => T.chain_(push(input), Chunk.mapEffect(f)))); } /** * Effectually transforms the outputs of this transducer */ export function mapM(f) { return fa => mapM_(fa, f); } /** * Transforms the errors of this transducer. */ export function mapError_(pab, f) { return new Transducer(M.map_(pab.push, push => is => T.mapError_(push(is), f))); } /** * Transforms the errors of this transducer. */ export function mapError(f) { return pab => mapError_(pab, f); } /** * Creates a transducer that always fails with the specified failure. */ export function fail(e) { return new Transducer(M.succeed(_ => T.fail(e))); } /** * Creates a transducer that always dies with the specified exception. */ export function die(error) { return new Transducer(M.succeed(_ => T.die(error))); } /** * Creates a transducer that always fails with the specified cause. */ export function halt(c) { return new Transducer(M.succeed(_ => T.halt(c))); } /** * The identity transducer. Passes elements through. */ export function identity() { return fromPush(O.fold(() => T.succeed(Chunk.empty()), T.succeed)); } /** * Creates a transducer from a chunk processing function. */ export function fromPush(push) { return new Transducer(M.succeed(push)); } /** * Creates a transducer that always evaluates the specified effect. */ export function fromEffect(task) { return new Transducer(M.succeed(_ => T.map_(task, Chunk.single))); } /** * Creates a transducer that purely transforms incoming values. */ export function fromFunction(f) { return map_(identity(), f); } /** * Creates a transducer that effectfully transforms incoming values. */ export function fromFunctionM(f) { return mapM_(identity(), f); } /** * Creates a transducer that returns the first element of the stream, if it exists. */ export function head() { return foldLeft(O.none, (acc, o) => O.fold_(acc, () => O.some(o), () => acc)); } /** * Creates a transducer that returns the last element of the stream, if it exists. */ export function last() { return foldLeft(O.none, (_, o) => O.some(o)); } /** * Emits the provided chunk before emitting any other value. */ export function prepend(values) { return new Transducer(M.map_(R.makeManagedRef(values), state => is => O.fold_(is, () => R.getAndSet_(state, Chunk.empty()), os => T.map_(R.getAndSet_(state, Chunk.empty()), c => Chunk.isEmpty(c) ? os : Chunk.concat_(c, os))))); } /** * Reads the first n values from the stream and uses them to choose the transducer that will be used for the remainder of the stream. * If the stream ends before it has collected n values the partial chunk will be provided to f. */ export function branchAfter(n, f) { const initialState = { _tag: "Collecting", data: Chunk.empty() }; const toCollect = Math.max(0, n); return new Transducer(M.chain_(M.scope, allocate => M.map_(RM.makeManagedRefM(initialState), state => is => O.fold_(is, () => T.chain_(RM.getAndSet_(state, initialState), s => { switch (s._tag) { case "Collecting": { return M.use_(f(s.data).push, f => f(O.none)); } case "Emitting": { return T.zipLeft_(s.push(O.none), s.finalizer(Ex.unit)); } } }), data => RM.modify_(state, s => { switch (s._tag) { case "Emitting": { return T.map_(s.push(O.some(data)), _ => Tp.tuple(_, s)); } case "Collecting": { if (Chunk.isEmpty(data)) { return T.succeed(Tp.tuple(Chunk.empty(), s)); } else { const remaining = toCollect - Chunk.size(s.data); if (remaining <= Chunk.size(data)) { const { tuple: [newCollected, remainder] } = Chunk.splitAt_(data, remaining); return T.chain_(allocate(f(Chunk.concat_(s.data, newCollected)).push), ({ tuple: [finalizer, push] }) => T.map_(push(O.some(remainder)), _ => Tp.tuple(_, { _tag: "Emitting", finalizer, push }))); } else { return T.succeed(Tp.tuple(Chunk.empty(), { _tag: "Collecting", data: Chunk.concat_(s.data, data) })); } } } } }))))); } /** * Creates a transducer that starts consuming values as soon as one fails * the predicate `p`. */ export function dropWhile(predicate) { return new Transducer(M.map_(R.makeManagedRef(true), dropping => is => O.fold_(is, () => T.succeed(Chunk.empty()), is => R.modify_(dropping, b => { switch (b) { case true: { const is1 = Chunk.dropWhile_(is, predicate); return Tp.tuple(is1, Chunk.isEmpty(is1)); } case false: { return Tp.tuple(is, false); } } })))); } /** * Creates a transducer that starts consuming values as soon as one fails * the effectful predicate `p`. */ export function dropWhileM(p) { return new Transducer(M.map_(M.let_(M.bind_(M.do, "dropping", () => R.makeManagedRef(true)), "push", ({ dropping }) => is => O.fold_(is, () => T.succeed(Chunk.empty()), is => T.chain_(T.chain_(dropping.get, b => b ? T.map_(Chunk.dropWhileEffect_(is, p), l => [l, Chunk.isEmpty(l)]) : T.succeed([is, false])), ([is, pt]) => T.as_(dropping.set(pt), is)))), ({ push }) => push)); } function foldGo(in_, state, progress, initial, contFn, f) { return Chunk.reduce_(in_, [Chunk.empty(), state, progress], ([os0, state, _], i) => { const o = f(state, i); if (contFn(o)) { return [os0, o, true]; } else { return [Chunk.append_(os0, o), initial, false]; } }); } /** * Creates a transducer by folding over a structure of type `O` for as long as * `contFn` results in `true`. The transducer will emit a value when `contFn` * evaluates to `false` and then restart the folding. */ export function fold(initial, contFn, f) { return new Transducer(M.map_(R.makeManagedRef(O.some(initial)), state => is => O.fold_(is, () => T.map_(R.getAndSet_(state, O.none), O.fold(() => Chunk.empty(), Chunk.single)), in_ => R.modify_(state, s => { const [o, s2, progress] = foldGo(in_, O.getOrElse_(s, () => initial), O.isSome(s), initial, contFn, f); if (progress) { return Tp.tuple(o, O.some(s2)); } else { return Tp.tuple(o, O.none); } })))); } /** * Creates a transducer by folding over a structure of type `O`. The transducer will * fold the inputs until the stream ends, resulting in a stream with one element. */ export function foldLeft(initial, f) { return fold(initial, () => true, f); } /** * Creates a sink by effectfully folding over a structure of type `S`. */ export function foldM(initial, contFn, f) { const init = O.some(initial); const go = (in_, state, progress) => Chunk.reduce_(in_, T.succeed([Chunk.empty(), state, progress]), (b, i) => T.chain_(b, ([os0, state, _]) => T.map_(f(state, i), o => { if (contFn(o)) { return [os0, o, true]; } else { return [Chunk.append_(os0, o), initial, false]; } }))); return new Transducer(M.map_(R.makeManagedRef(init), state => is => O.fold_(is, () => T.map_(R.getAndSet_(state, O.none), O.fold(() => Chunk.empty(), Chunk.single)), in_ => T.chain_(T.chain_(state.get, s => go(in_, O.getOrElse_(s, () => initial), O.isSome(s))), ([os, s, progress]) => progress ? T.zipRight_(state.set(O.some(s)), T.succeed(os)) : T.zipRight_(state.set(O.none), T.succeed(os)))))); } /** * Creates a transducer by effectfully folding over a structure of type `O`. The transducer will * fold the inputs until the stream ends, resulting in a stream with one element. */ export function foldLeftM(initial, f) { return foldM(initial, () => true, f); } /** * Creates a transducer that folds elements of type `I` into a structure * of type `O` until `max` elements have been folded. * * Like `foldWeighted`, but with a constant cost function of 1. */ export function foldUntil(initial, max, f) { return map(t => t[0])(fold(tuple(initial, 0), ([_, n]) => n < max, ([o, count], i) => [f(o, i), count + 1])); } /** * Creates a transducer that effectfully folds elements of type `I` into a structure * of type `O` until `max` elements have been folded. * * Like `foldWeightedM`, but with a constant cost function of 1. */ export function foldUntilM(initial, max, f) { return map(t => t[0])(foldM(tuple(initial, 0), ([_, n]) => n < max, ([o, count], i) => T.map_(f(o, i), o => [o, count + 1]))); } /** * Creates a transducer that folds elements of type `I` into a structure * of type `O`, until `max` worth of elements (determined by the `costFn`) * have been folded. * * The `decompose` function will be used for decomposing elements that * cause an `O` aggregate to cross `max` into smaller elements. * * Be vigilant with this function, it has to generate "simpler" values * or the fold may never end. A value is considered indivisible if * `decompose` yields the empty chunk or a single-valued chunk. In * these cases, there is no other choice than to yield a value that * will cross the threshold. * * The foldWeightedDecomposeM allows the decompose function * to return an `Effect` value, and consequently it allows the transducer * to fail. */ export function foldWeightedDecompose(initial, costFn, max, decompose, f) { const initialState = { result: initial, cost: 0 }; const go = (in_, os0, state, dirty) => Chunk.reduce_(in_, [os0, state, dirty], ([os0, state, _], i) => { const total = state.cost + costFn(state.result, i); if (total > max) { const is = decompose(i); if (Chunk.size(is) <= 1 && !dirty) { return [Chunk.append_(os0, f(state.result, !Chunk.isEmpty(is) ? Chunk.unsafeGet_(is, 0) : i)), initialState, false]; } else if (Chunk.size(is) <= 1 && dirty) { const elem = !Chunk.isEmpty(is) ? Chunk.unsafeGet_(is, 0) : i; return [Chunk.append_(os0, state.result), { result: f(initialState.result, elem), cost: costFn(initialState.result, elem) }, true]; } else { return go(is, os0, state, dirty); } } else { return [os0, { result: f(state.result, i), cost: total }, true]; } }); return new Transducer(M.map_(R.makeManagedRef(O.some(initialState)), state => is => O.fold_(is, () => T.map_(R.getAndSet_(state, O.none), O.fold(() => Chunk.empty(), s => Chunk.single(s.result))), in_ => R.modify_(state, s => { const [o, s2, dirty] = go(in_, Chunk.empty(), O.getOrElse_(s, () => initialState), O.isSome(s)); if (dirty) { return Tp.tuple(o, O.some(s2)); } else { return Tp.tuple(o, O.none); } })))); } /** * Creates a transducer that effectfully folds elements of type `I` into a structure * of type `S`, until `max` worth of elements (determined by the `costFn`) have * been folded. * * The `decompose` function will be used for decomposing elements that * cause an `S` aggregate to cross `max` into smaller elements. Be vigilant with * this function, it has to generate "simpler" values or the fold may never end. * A value is considered indivisible if `decompose` yields the empty chunk or a * single-valued chunk. In these cases, there is no other choice than to yield * a value that will cross the threshold. * * See foldWeightedDecompose for an example. */ export function foldWeightedDecomposeM(initial, costFn, max, decompose, f) { const initialState = { result: initial, cost: 0 }; const go = (in_, os, state, dirty) => Chunk.reduce_(in_, T.succeed([os, state, dirty]), (o, i) => T.chain_(o, ([os, state, _]) => T.chain_(costFn(state.result, i), cost => { const total = cost + state.cost; if (total > max) { return T.chain_(decompose(i), is => { if (Chunk.size(is) <= 1 && !dirty) { return T.map_(f(state.result, !Chunk.isEmpty(is) ? Chunk.unsafeGet_(is, 0) : i), o => [Chunk.append_(os, o), initialState, false]); } else if (Chunk.size(is) <= 1 && dirty) { const elem = !Chunk.isEmpty(is) ? Chunk.unsafeGet_(is, 0) : i; return T.zipWith_(f(initialState.result, elem), costFn(initialState.result, elem), (result, cost) => [Chunk.append_(os, state.result), { result, cost }, true]); } else { return go(is, os, state, dirty); } }); } else { return T.map_(f(state.result, i), o => [os, { result: o, cost: total }, true]); } }))); return new Transducer(M.map_(R.makeManagedRef(O.some(initialState)), state => is => O.fold_(is, () => T.map_(R.getAndSet_(state, O.none), O.fold(() => Chunk.empty(), s => Chunk.single(s.result))), in_ => T.chain_(T.chain_(state.get, s => go(in_, Chunk.empty(), O.getOrElse_(s, () => initialState), O.isSome(s))), ([os, s, dirty]) => dirty ? T.zipRight_(state.set(O.some(s)), T.succeed(os)) : T.zipRight_(state.set(O.none), T.succeed(os)))))); } /** * Creates a transducer that folds elements of type `I` into a structure * of type `O`, until `max` worth of elements (determined by the `costFn`) * have been folded. * * @note Elements that have an individual cost larger than `max` will * force the transducer to cross the `max` cost. See `foldWeightedDecompose` * for a variant that can handle these cases. */ export function foldWeighted(initial, costFn, max, f) { return foldWeightedDecompose(initial, costFn, max, Chunk.single, f); } function collectAllNGo(n, in_, leftover, acc) { // eslint-disable-next-line no-constant-condition while (1) { const { tuple: [left, nextIn] } = Chunk.splitAt_(in_, n - Chunk.size(leftover)); if (Chunk.size(leftover) + Chunk.size(left) < n) return Tp.tuple(acc, Chunk.concat_(leftover, left));else { const nextOut = !Chunk.isEmpty(leftover) ? Chunk.append_(acc, Chunk.concat_(leftover, left)) : Chunk.append_(acc, left); in_ = nextIn; leftover = Chunk.empty(); acc = nextOut; } } throw new Error("Bug"); } /** * Creates a transducer accumulating incoming values into chunks of maximum size `n`. */ export function collectAllN(n) { return new Transducer(M.map_(R.makeManagedRef(Chunk.empty()), state => is => O.fold_(is, () => T.map_(R.getAndSet_(state, Chunk.empty()), leftover => !Chunk.isEmpty(leftover) ? Chunk.single(leftover) : Chunk.empty()), in_ => R.modify_(state, leftover => collectAllNGo(n, in_, leftover, Chunk.empty()))))); } /** * Creates a transducer accumulating incoming values into maps of up to `n` keys. Elements * are mapped to keys using the function `key`; elements mapped to the same key will * be merged with the function `f`. */ export function collectAllToMapN(n, key, merge) { return filter(not(Map.isEmpty))(foldWeighted(Map.empty, (acc, i) => acc.has(key(i)) ? 0 : 1, n, (acc, i) => { const k = key(i); if (acc.has(k)) return Map.insert(k, merge(acc.get(k), i))(acc);else return Map.insert(k, i)(acc); })); } /** * Accumulates incoming elements into a chunk as long as they verify predicate `p`. */ export function collectAllWhile(p) { return filter(x => !Chunk.isEmpty(x))(map(t => t[0])(fold([Chunk.empty(), true], t => t[1], ([is, _], i) => p(i) ? [Chunk.append_(is, i), true] : [is, false]))); } /** * Accumulates incoming elements into a chunk as long as they verify effectful predicate `p`. */ export function collectAllWhileM(p) { return filter(x => !Chunk.isEmpty(x))(map(t => t[0])(foldM([Chunk.empty(), true], t => t[1], ([is, _], i) => T.map_(p(i), b => b ? [Chunk.append_(is, i), true] : [is, false])))); } export function filter_(fa, predicate) { return new Transducer(M.map_(fa.push, push => is => T.map_(push(is), Chunk.filter(predicate)))); } export function filter(predicate) { return fa => filter_(fa, predicate); } export function filterInput_(fa, predicate) { return new Transducer(M.map_(fa.push, push => is => push(O.map_(is, Chunk.filter(predicate))))); } export function filterInput(predicate) { return fa => filterInput_(fa, predicate); } /** * Effectually filters the inputs of this transducer. */ export function filterInputM_(fa, predicate) { return new Transducer(M.map_(fa.push, push => is => O.fold_(is, () => push(O.none), x => T.chain_(Chunk.filterEffect_(x, predicate), in_ => push(O.some(in_)))))); } /** * Effectually filters the inputs of this transducer. */ export function filterInputM(predicate) { return fa => filterInputM_(fa, predicate); } /** * Creates a transducer produced from an effect. */ export function unwrap(effect) { return unwrapManaged(T.toManaged(effect)); } /** * Creates a transducer produced from a managed effect. */ export function unwrapManaged(managed) { return new Transducer(M.chain_(M.fold_(managed, err => fail(err), _ => _), _ => _.push)); } //# sourceMappingURL=index.mjs.map