@effect-ts/system
Version:
Effect-TS is a zero dependency set of libraries to write highly productive, purely functional TypeScript at scale.
1,124 lines (1,063 loc) • 32.5 kB
text/typescript
// ets_tracing: off
import "../../Operator/index.js"
import type * as C from "../../Cause/index.js"
import * as Chunk from "../../Collections/Immutable/Chunk/index.js"
import * as Map from "../../Collections/Immutable/Map/index.js"
import * as Tp from "../../Collections/Immutable/Tuple/index.js"
import * as Ex from "../../Exit/index.js"
import type { Predicate, Refinement } from "../../Function/index.js"
import { not, pipe, tuple } from "../../Function/index.js"
import type { Finalizer } from "../../Managed/ReleaseMap/index.js"
import * as O from "../../Option/index.js"
import * as RM from "../../RefM/index.js"
import * as T from "../_internal/effect.js"
import * as M from "../_internal/managed.js"
import * as R from "../_internal/ref.js"
// 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<R, E, I, O> {
constructor(
readonly push: M.Managed<
R,
never,
(c: O.Option<Chunk.Chunk<I>>) => T.Effect<R, E, Chunk.Chunk<O>>
>
) {}
}
/**
* 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 = <R, E, I, O, R1>(
push: M.Managed<
R,
never,
(c: O.Option<Chunk.Chunk<I>>) => T.Effect<R1, E, Chunk.Chunk<O>>
>
) => new Transducer<R & R1, E, I, O>(push)
/**
* Compose this transducer with another transducer, resulting in a composite transducer.
*/
export const andThen =
<R1, E1, O, O1>(that: Transducer<R1, E1, O, O1>) =>
<R, E, I>(self: Transducer<R, E, I, O>): Transducer<R & R1, E1 | E, I, O1> =>
transducer(
pipe(
self.push,
M.zipWith(that.push, (pushLeft, pushRight) =>
O.fold(
() =>
pipe(
pushLeft(O.none),
T.chain((cl) =>
Chunk.isEmpty(cl)
? pushRight(O.none)
: pipe(
pushRight(O.some(cl)),
T.zipWith(pushRight(O.none), Chunk.concat_)
)
)
),
(inputs) =>
pipe(
pushLeft(O.some(inputs)),
T.chain((cl) => pushRight(O.some(cl)))
)
)
)
)
)
/**
* Transforms the outputs of this transducer.
*/
export function map_<R, E, I, O, O1>(
fa: Transducer<R, E, I, O>,
f: (o: O) => O1
): Transducer<R, E, I, O1> {
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<O, P>(
f: (o: O) => P
): <R, E, I>(fa: Transducer<R, E, I, O>) => Transducer<R, E, I, P> {
return (fa) => map_(fa, f)
}
/**
* Transforms the chunks emitted by this transducer.
*/
export function mapChunks_<R, E, I, O, O1>(
fa: Transducer<R, E, I, O>,
f: (chunks: Chunk.Chunk<O>) => Chunk.Chunk<O1>
): Transducer<R, E, I, O1> {
return new Transducer(M.map_(fa.push, (push) => (input) => T.map_(push(input), f)))
}
/**
* Transforms the chunks emitted by this transducer.
*/
export function mapChunks<O, O1>(
f: (chunks: Chunk.Chunk<O>) => Chunk.Chunk<O1>
): <R, E, I>(fa: Transducer<R, E, I, O>) => Transducer<R, E, I, O1> {
return (fa) => mapChunks_(fa, f)
}
/**
* Effectfully transforms the chunks emitted by this transducer.
*/
export function mapChunksM_<R, E, I, O, R1, E1, O1>(
fa: Transducer<R, E, I, O>,
f: (chunk: Chunk.Chunk<O>) => T.Effect<R1, E1, Chunk.Chunk<O1>>
): Transducer<R & R1, E | E1, I, O1> {
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<O, R1, E1, O1>(
f: (chunk: Chunk.Chunk<O>) => T.Effect<R1, E1, Chunk.Chunk<O1>>
): <R, E, I>(fa: Transducer<R, E, I, O>) => Transducer<R & R1, E | E1, I, O1> {
return (fa) => mapChunksM_(fa, f)
}
/**
* Effectually transforms the outputs of this transducer
*/
export function mapM_<R, E, I, O, R1, E1, O1>(
fa: Transducer<R, E, I, O>,
f: (o: O) => T.Effect<R1, E1, O1>
): Transducer<R & R1, E | E1, I, O1> {
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<O, R1, E1, O1>(
f: (o: O) => T.Effect<R1, E1, O1>
): <R, E, I>(fa: Transducer<R, E, I, O>) => Transducer<R & R1, E | E1, I, O1> {
return (fa) => mapM_(fa, f)
}
/**
* Transforms the errors of this transducer.
*/
export function mapError_<R, E, I, O, E1>(
pab: Transducer<R, E, I, O>,
f: (e: E) => E1
): Transducer<R, E1, I, O> {
return new Transducer(M.map_(pab.push, (push) => (is) => T.mapError_(push(is), f)))
}
/**
* Transforms the errors of this transducer.
*/
export function mapError<E, E1>(
f: (e: E) => E1
): <R, I, O>(pab: Transducer<R, E, I, O>) => Transducer<R, E1, I, O> {
return (pab) => mapError_(pab, f)
}
/**
* Creates a transducer that always fails with the specified failure.
*/
export function fail<E>(e: E): Transducer<unknown, E, unknown, never> {
return new Transducer(M.succeed((_) => T.fail(e)))
}
/**
* Creates a transducer that always dies with the specified exception.
*/
export function die(error: unknown): Transducer<unknown, never, unknown, never> {
return new Transducer(M.succeed((_) => T.die(error)))
}
/**
* Creates a transducer that always fails with the specified cause.
*/
export function halt<E>(c: C.Cause<E>): Transducer<unknown, E, unknown, never> {
return new Transducer(M.succeed((_) => T.halt(c)))
}
/**
* The identity transducer. Passes elements through.
*/
export function identity<I>(): Transducer<unknown, never, I, I> {
return fromPush(O.fold(() => T.succeed(Chunk.empty()), T.succeed))
}
/**
* Creates a transducer from a chunk processing function.
*/
export function fromPush<R, E, I, O>(
push: (input: O.Option<Chunk.Chunk<I>>) => T.Effect<R, E, Chunk.Chunk<O>>
): Transducer<R, E, I, O> {
return new Transducer(M.succeed(push))
}
/**
* Creates a transducer that always evaluates the specified effect.
*/
export function fromEffect<R, E, A>(
task: T.Effect<R, E, A>
): Transducer<R, E, unknown, A> {
return new Transducer(M.succeed((_: any) => T.map_(task, Chunk.single)))
}
/**
* Creates a transducer that purely transforms incoming values.
*/
export function fromFunction<I, O>(f: (i: I) => O): Transducer<unknown, never, I, O> {
return map_(identity(), f)
}
/**
* Creates a transducer that effectfully transforms incoming values.
*/
export function fromFunctionM<R, E, I, O>(
f: (i: I) => T.Effect<R, E, O>
): Transducer<R, E, I, O> {
return mapM_(identity(), f)
}
/**
* Creates a transducer that returns the first element of the stream, if it exists.
*/
export function head<O>(): Transducer<unknown, never, O, O.Option<O>> {
return foldLeft(O.none as O.Option<O>, (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<O>(): Transducer<unknown, never, O, O.Option<O>> {
return foldLeft(O.none as O.Option<O>, (_, o) => O.some(o))
}
/**
* Emits the provided chunk before emitting any other value.
*/
export function prepend<O>(values: Chunk.Chunk<O>): Transducer<unknown, never, O, O> {
return new Transducer(
M.map_(
R.makeManagedRef(values),
(state) => (is: O.Option<Chunk.Chunk<O>>) =>
O.fold_(
is,
() => R.getAndSet_(state, Chunk.empty()),
(os) =>
pipe(
state,
R.getAndSet(Chunk.empty()),
T.map((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<R, E, I, O>(
n: number,
f: (c: Chunk.Chunk<I>) => Transducer<R, E, I, O>
): Transducer<R, E, I, O> {
interface Collecting {
_tag: "Collecting"
data: Chunk.Chunk<I>
}
interface Emitting {
_tag: "Emitting"
finalizer: Finalizer
push: (is: O.Option<Chunk.Chunk<I>>) => T.Effect<R, E, Chunk.Chunk<O>>
}
type State = Collecting | Emitting
const initialState: State = {
_tag: "Collecting",
data: Chunk.empty()
}
const toCollect = Math.max(0, n)
return new Transducer(
M.chain_(M.scope, (allocate) =>
M.map_(
RM.makeManagedRefM<State>(initialState),
(state) => (is: O.Option<Chunk.Chunk<I>>) =>
O.fold_(
is,
() =>
pipe(
RM.getAndSet_(state, initialState),
T.chain((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<O>(), 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<[Chunk.Chunk<O>, State]>(_, {
_tag: "Emitting",
finalizer,
push
})
)
)
} else {
return T.succeed(
Tp.tuple<[Chunk.Chunk<O>, State]>(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<I>(
predicate: Predicate<I>
): Transducer<unknown, never, I, I> {
return new Transducer(
M.map_(
R.makeManagedRef(true),
(dropping) => (is: O.Option<Chunk.Chunk<I>>) =>
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<R, E, I>(
p: (i: I) => T.Effect<R, E, boolean>
): Transducer<R, E, I, I> {
return new Transducer(
pipe(
M.do,
M.bind("dropping", () => R.makeManagedRef(true)),
M.let(
"push",
({ dropping }) =>
(is: O.Option<Chunk.Chunk<I>>) =>
O.fold_(
is,
() => T.succeed(Chunk.empty<I>()),
(is) =>
pipe(
dropping.get,
T.chain((b) =>
b
? T.map_(
Chunk.dropWhileEffect_(is, p),
(l: Chunk.Chunk<I>) => [l, Chunk.isEmpty(l)] as const
)
: T.succeed([is, false] as const)
),
T.chain(([is, pt]) => T.as_(dropping.set(pt), is))
)
)
),
M.map(({ push }) => push)
)
)
}
function foldGo<I, O>(
in_: Chunk.Chunk<I>,
state: O,
progress: boolean,
initial: O,
contFn: (o: O) => boolean,
f: (output: O, input: I) => O
): readonly [Chunk.Chunk<O>, O, boolean] {
return Chunk.reduce_(
in_,
[Chunk.empty<O>(), state, progress] as const,
([os0, state, _], i) => {
const o = f(state, i)
if (contFn(o)) {
return [os0, o, true] as const
} else {
return [Chunk.append_(os0, o), initial, false] as const
}
}
)
}
/**
* 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<I, O>(
initial: O,
contFn: (o: O) => boolean,
f: (output: O, input: I) => O
): Transducer<unknown, never, I, O> {
return new Transducer(
M.map_(
R.makeManagedRef(O.some(initial)),
(state) => (is: O.Option<Chunk.Chunk<I>>) =>
O.fold_(
is,
() =>
pipe(
R.getAndSet_(state, O.none),
T.map(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<I, O>(
initial: O,
f: (output: O, input: I) => O
): Transducer<unknown, never, I, O> {
return fold(initial, () => true, f)
}
/**
* Creates a sink by effectfully folding over a structure of type `S`.
*/
export function foldM<R, E, I, O>(
initial: O,
contFn: (o: O) => boolean,
f: (output: O, input: I) => T.Effect<R, E, O>
): Transducer<R, E, I, O> {
const init = O.some(initial)
const go = (
in_: Chunk.Chunk<I>,
state: O,
progress: boolean
): T.Effect<R, E, readonly [Chunk.Chunk<O>, O, boolean]> =>
Chunk.reduce_(
in_,
T.succeed([Chunk.empty(), state, progress]) as T.Effect<
R,
E,
readonly [Chunk.Chunk<O>, O, boolean]
>,
(b, i) =>
T.chain_(b, ([os0, state, _]) =>
T.map_(f(state, i), (o) => {
if (contFn(o)) {
return [os0, o, true] as const
} else {
return [Chunk.append_(os0, o), initial, false] as const
}
})
)
)
return new Transducer(
M.map_(
R.makeManagedRef(init),
(state) => (is: O.Option<Chunk.Chunk<I>>) =>
O.fold_(
is,
() =>
pipe(
state,
R.getAndSet(O.none as O.Option<O>),
T.map(O.fold(() => Chunk.empty(), Chunk.single))
),
(in_) =>
pipe(
state.get,
T.chain((s) =>
go(
in_,
O.getOrElse_(s, () => initial),
O.isSome(s)
)
),
T.chain(([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<R, E, I, O>(
initial: O,
f: (output: O, input: I) => T.Effect<R, E, O>
): Transducer<R, E, I, O> {
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<I, O>(
initial: O,
max: number,
f: (output: O, input: I) => O
): Transducer<unknown, never, I, O> {
return pipe(
fold(
tuple(initial, 0),
([_, n]) => n < max,
([o, count], i: I) => [f(o, i), count + 1] as const
),
map((t) => t[0])
)
}
/**
* 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<R, E, I, O>(
initial: O,
max: number,
f: (output: O, input: I) => T.Effect<R, E, O>
): Transducer<R, E, I, O> {
return pipe(
foldM(
tuple(initial, 0),
([_, n]) => n < max,
([o, count], i: I) => T.map_(f(o, i), (o) => [o, count + 1] as const)
),
map((t) => t[0])
)
}
/**
* 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<I, O>(
initial: O,
costFn: (output: O, input: I) => number,
max: number,
decompose: (input: I) => Chunk.Chunk<I>,
f: (output: O, input: I) => O
): Transducer<unknown, never, I, O> {
interface FoldWeightedState {
result: O
cost: number
}
const initialState: FoldWeightedState = {
result: initial,
cost: 0
}
const go = (
in_: Chunk.Chunk<I>,
os0: Chunk.Chunk<O>,
state: FoldWeightedState,
dirty: boolean
): readonly [Chunk.Chunk<O>, FoldWeightedState, boolean] =>
Chunk.reduce_(in_, [os0, state, dirty] as const, ([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
] as const
} 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
] as const
} else {
return go(is, os0, state, dirty)
}
} else {
return [os0, { result: f(state.result, i), cost: total }, true] as const
}
})
return new Transducer(
M.map_(
R.makeManagedRef(O.some(initialState)),
(state) => (is: O.Option<Chunk.Chunk<I>>) =>
O.fold_(
is,
() =>
pipe(
state,
R.getAndSet(O.none as O.Option<FoldWeightedState>),
T.map(
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<R, E, I, O>(
initial: O,
costFn: (output: O, input: I) => T.Effect<R, E, number>,
max: number,
decompose: (input: I) => T.Effect<R, E, Chunk.Chunk<I>>,
f: (output: O, input: I) => T.Effect<R, E, O>
): Transducer<R, E, I, O> {
interface FoldWeightedState {
result: O
cost: number
}
const initialState: FoldWeightedState = {
result: initial,
cost: 0
}
const go = (
in_: Chunk.Chunk<I>,
os: Chunk.Chunk<O>,
state: FoldWeightedState,
dirty: boolean
): T.Effect<R, E, readonly [Chunk.Chunk<O>, FoldWeightedState, boolean]> =>
Chunk.reduce_(
in_,
T.succeed([os, state, dirty]) as T.Effect<
R,
E,
readonly [Chunk.Chunk<O>, FoldWeightedState, boolean]
>,
(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] as const
)
} 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] as const
)
}
})
)
)
return new Transducer(
M.map_(
R.makeManagedRef(O.some(initialState)),
(state) => (is: O.Option<Chunk.Chunk<I>>) =>
O.fold_(
is,
() =>
pipe(
state,
R.getAndSet(O.none as O.Option<FoldWeightedState>),
T.map(
O.fold(
() => Chunk.empty(),
(s) => Chunk.single(s.result)
)
)
),
(in_) =>
pipe(
state.get,
T.chain((s) =>
go(
in_,
Chunk.empty(),
O.getOrElse_(s, () => initialState),
O.isSome(s)
)
),
T.chain(([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<I, O>(
initial: O,
costFn: (o: O, i: I) => number,
max: number,
f: (o: O, i: I) => O
): Transducer<unknown, never, I, O> {
return foldWeightedDecompose(initial, costFn, max, Chunk.single, f)
}
function collectAllNGo<I>(
n: number,
in_: Chunk.Chunk<I>,
leftover: Chunk.Chunk<I>,
acc: Chunk.Chunk<Chunk.Chunk<I>>
): Tp.Tuple<[Chunk.Chunk<Chunk.Chunk<I>>, Chunk.Chunk<I>]> {
// 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<I>(
n: number
): Transducer<unknown, never, I, Chunk.Chunk<I>> {
return new Transducer(
M.map_(
R.makeManagedRef(Chunk.empty<I>()),
(state) => (is: O.Option<Chunk.Chunk<I>>) =>
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<K, I>(
n: number,
key: (i: I) => K,
merge: (i: I, i1: I) => I
): Transducer<unknown, never, I, ReadonlyMap<K, I>> {
return pipe(
foldWeighted<I, ReadonlyMap<K, I>>(
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) as I, i))(acc)
else return Map.insert(k, i)(acc)
}
),
filter(not(Map.isEmpty))
)
}
/**
* Accumulates incoming elements into a chunk as long as they verify predicate `p`.
*/
export function collectAllWhile<I>(
p: Predicate<I>
): Transducer<unknown, never, I, Chunk.Chunk<I>> {
return pipe(
fold<I, [Chunk.Chunk<I>, boolean]>(
[Chunk.empty(), true],
(t) => t[1],
([is, _], i) => (p(i) ? [Chunk.append_(is, i), true] : [is, false])
),
map((t) => t[0]),
filter((x) => !Chunk.isEmpty(x))
)
}
/**
* Accumulates incoming elements into a chunk as long as they verify effectful predicate `p`.
*/
export function collectAllWhileM<R, E, I>(
p: (i: I) => T.Effect<R, E, boolean>
): Transducer<R, E, I, Chunk.Chunk<I>> {
return pipe(
foldM<R, E, I, [Chunk.Chunk<I>, boolean]>(
[Chunk.empty(), true],
(t) => t[1],
([is, _], i) =>
T.map_(p(i), (b) => (b ? [Chunk.append_(is, i), true] : [is, false]))
),
map((t) => t[0]),
filter((x) => !Chunk.isEmpty(x))
)
}
/**
* Filters the outputs of this transducer.
*/
export function filter_<R, E, I, O>(
fa: Transducer<R, E, I, O>,
predicate: Predicate<O>
): Transducer<R, E, I, O>
export function filter_<R, E, I, O, B extends O>(
fa: Transducer<R, E, I, O>,
refinement: Refinement<O, B>
): Transducer<R, E, I, B>
export function filter_<R, E, I, O>(
fa: Transducer<R, E, I, O>,
predicate: Predicate<O>
): Transducer<R, E, I, O> {
return new Transducer(
M.map_(fa.push, (push) => (is) => T.map_(push(is), Chunk.filter(predicate)))
)
}
/**
* Filters the outputs of this transducer.
*/
export function filter<O>(
predicate: Predicate<O>
): <R, E, I>(fa: Transducer<R, E, I, O>) => Transducer<R, E, I, O>
export function filter<O, B extends O>(
refinement: Refinement<O, B>
): <R, E, I>(fa: Transducer<R, E, I, O>) => Transducer<R, E, I, B>
export function filter<O>(
predicate: Predicate<O>
): <R, E, I>(fa: Transducer<R, E, I, O>) => Transducer<R, E, I, O> {
return (fa) => filter_(fa, predicate)
}
/**
* Filters the inputs of this transducer.
*/
export function filterInput_<R, E, I, O>(
fa: Transducer<R, E, I, O>,
predicate: Predicate<I>
): Transducer<R, E, I, O>
export function filterInput_<R, E, I, O, I1 extends I>(
fa: Transducer<R, E, I, O>,
refinement: Refinement<I, I1>
): Transducer<R, E, I1, O>
export function filterInput_<R, E, I, O>(
fa: Transducer<R, E, I, O>,
predicate: Predicate<I>
): Transducer<R, E, I, O> {
return new Transducer(
M.map_(fa.push, (push) => (is) => push(O.map_(is, Chunk.filter(predicate))))
)
}
/**
* Filters the inputs of this transducer.
*/
export function filterInput<I>(
predicate: Predicate<I>
): <R, E, O>(fa: Transducer<R, E, I, O>) => Transducer<R, E, I, O>
export function filterInput<I, I1 extends I>(
refinement: Refinement<I, I1>
): <R, E, O>(fa: Transducer<R, E, I, O>) => Transducer<R, E, I1, O>
export function filterInput<I>(
predicate: Predicate<I>
): <R, E, O>(fa: Transducer<R, E, I, O>) => Transducer<R, E, I, O> {
return (fa) => filterInput_(fa, predicate)
}
/**
* Effectually filters the inputs of this transducer.
*/
export function filterInputM_<R, E, I, O, R1, E1>(
fa: Transducer<R, E, I, O>,
predicate: (i: I) => T.Effect<R1, E1, boolean>
): Transducer<R & R1, E | E1, I, O> {
return new Transducer(
M.map_(
fa.push,
(push) => (is) =>
O.fold_(
is,
() => push(O.none),
(x) =>
pipe(
x,
Chunk.filterEffect(predicate),
T.chain((in_) => push(O.some(in_)))
)
)
)
)
}
/**
* Effectually filters the inputs of this transducer.
*/
export function filterInputM<I, R1, E1>(
predicate: (i: I) => T.Effect<R1, E1, boolean>
): <R, E, O>(fa: Transducer<R, E, I, O>) => Transducer<R & R1, E | E1, I, O> {
return (fa) => filterInputM_(fa, predicate)
}
/**
* Creates a transducer produced from an effect.
*/
export function unwrap<R, E, I, O>(
effect: T.Effect<R, E, Transducer<R, E, I, O>>
): Transducer<R, E, I, O> {
return unwrapManaged(T.toManaged(effect))
}
/**
* Creates a transducer produced from a managed effect.
*/
export function unwrapManaged<R, E, I, O>(
managed: M.Managed<R, E, Transducer<R, E, I, O>>
): Transducer<R, E, I, O> {
return new Transducer(
M.chain_(
M.fold_(
managed,
(err) => fail<E>(err),
(_) => _
),
(_) => _.push
)
)
}