UNPKG

@typed/fp

Version:

Data Structures and Resources for fp-ts

627 lines 17.3 kB
/** * Stream is an extension of @most/core with additional * fp-ts instances as well as additional combinators for interoperation with other data * structures in @typed/fp and fp-ts. * * A large goal of @typed/fp is to expand the `fp-ts` ecosystem to include * [@most/core](https://github.com/mostjs/core) for a Reactive programming style, including * derivatives such as [ReaderStream](./ReaderStream.ts.md), [ReaderStreamEither](./ReaderStreamEither.ts.md), * [StateReaderStreamEither](./StateReaderStreamEither.ts.md) and a few others. It's the fastest push-based * reactive library in JS period. The performance characteristics are due to it's architecture of getting out of * the way of the computations you need to perform. It's also the first experience I had with FP. For instance, * Most utilizes `Functor` laws to remove unneeded machinery through function composition improving runtime * performance amongst other optimizations. * * See the [@most/core Documentation](https://mostcore.readthedocs.io/en/latest/) for the remaining API * exposed by this module. Both @most/core + @most/types are re-exported from this module * @since 0.9.2 */ import * as M from '@most/core'; import { disposeAll, disposeNone } from '@most/disposable'; import { asap, schedulerRelativeTo } from '@most/scheduler'; import * as App from 'fp-ts/Applicative'; import * as Ap from 'fp-ts/Apply'; import * as CH from 'fp-ts/Chain'; import { isLeft, isRight, left, match, right } from 'fp-ts/Either'; import { constVoid, flow, pipe } from 'fp-ts/function'; import { bindTo as bindTo_, tupled as tupled_ } from 'fp-ts/Functor'; import * as O from 'fp-ts/Option'; import * as RA from 'fp-ts/ReadonlyArray'; import * as RM from 'fp-ts/ReadonlyMap'; import { fst, snd } from 'fp-ts/Tuple2'; import { create } from './Adapter'; import { disposeBoth, settable } from './Disposable'; import { deepEqualsEq } from './Eq'; import * as FRe from './FromResume'; import * as R from './Resume'; import * as S from './struct'; /** * Convert an IO<Disposable> into a Most.js Task * @since 0.9.2 * @category Constructor */ export function createCallbackTask(cb, onError) { const disposable = settable(); return { run(t) { if (!disposable.isDisposed()) { disposable.addDisposable(cb(t)); } }, error(_, e) { disposable.dispose(); if (onError) { return onError(e); } throw e; }, dispose: disposable.dispose, }; } /** * @since 0.9.2 * @category URI */ export const URI = '@most/core/Stream'; /** * Create a Stream monoid where concat is a parallel merge. */ /** * @since 0.9.2 * @category Typeclass Constructor */ export const getMonoid = () => { return { concat: M.merge, empty: M.empty(), }; }; /** * Filter Option's from within a Stream */ /** * @since 0.9.2 * @category Combinator */ export const compact = (stream) => M.map((s) => s.value, M.filter(O.isSome, stream)); /** * Separate left and right values */ /** * @since 0.9.2 * @category Combinator */ export const separate = (stream) => { const s = M.multicast(stream); const left = pipe(s, M.filter(isLeft), M.map((l) => l.left)); const right = pipe(s, M.filter(isRight), M.map((r) => r.right)); return { left, right }; }; /** * @since 0.9.2 * @category Combinator */ export const partitionMap = (f) => (fa) => separate(M.map(f, fa)); /** * @since 0.9.2 * @category Combinator */ export const partition = (predicate) => partitionMap((a) => (predicate(a) ? right(a) : left(a))); /** * @since 0.9.2 * @category Combinator */ export const filterMap = (f) => (fa) => compact(M.map(f, fa)); /** * @since 0.9.2 * @category Instance */ export const Functor = { map: M.map, }; /** * @since 0.9.2 * @category Instance */ export const Pointed = { of: M.now, }; /** * @since 0.9.2 * @category Constructor */ export const of = Pointed.of; /** * @since 0.9.2 * @category Instance */ export const Apply = { ...Functor, ap: M.ap, }; /** * @since 0.9.2 * @category Combinator */ export const apFirst = Ap.apFirst(Apply); /** * @since 0.9.2 * @category Combinator */ export const apS = Ap.apS(Apply); /** * @since 0.9.2 * @category Combinator */ export const apSecond = Ap.apSecond(Apply); /** * @since 0.9.2 * @category Combinator */ export const apT = Ap.apT(Apply); /** * @since 0.9.2 * @category Typeclass Constructor */ export const getApplySemigroup = Ap.getApplySemigroup(Apply); /** * @since 0.9.2 * @category Instance */ export const Applicative = { ...Apply, ...Pointed, }; /** * @since 0.9.2 * @category Typeclass Constructor */ export const getApplicativeMonoid = App.getApplicativeMonoid(Applicative); /** * @since 0.9.2 * @category Instance */ export const Chain = { ...Functor, chain: M.chain, }; /** * @since 0.9.2 * @category Combinator */ export const chainFirst = CH.chainFirst(Chain); /** * @since 0.9.2 * @category Combinator */ export const bind = CH.bind(Chain); /** * @since 0.9.2 * @category Instance */ export const Monad = { ...Chain, ...Pointed, }; /** * @since 0.9.2 * @category Combinator */ export const chainRec = (f) => (value) => pipe(value, f, M.delay(0), M.chain(match(flow(chainRec(f)), M.now))); /** * @since 0.9.2 * @category Instance */ export const ChainRec = { chainRec, }; /** * @since 0.9.2 * @category Combinator */ export const switchRec = (f) => (value) => pipe(value, f, M.map(match(switchRec(f), M.now)), M.switchLatest); /** * @since 0.9.2 * @category Instance */ export const SwitchRec = { chainRec: switchRec, }; /** * @since 0.9.2 * @category Combinator */ export const mergeConcurrentlyRec = (concurrency) => (f) => (value) => pipe(value, f, M.map(match(mergeConcurrentlyRec(concurrency)(f), M.now)), M.mergeConcurrently(concurrency)); /** * @since 0.9.2 * @category Typeclass Constructor */ export const getConcurrentChainRec = (concurrency) => ({ chainRec: mergeConcurrentlyRec(concurrency), }); /** * @since 0.9.2 * @category Instance */ export const FromIO = { fromIO: (f) => Functor.map(f)(M.now(undefined)), }; /** * @since 0.9.2 * @category Constructor */ export const fromIO = FromIO.fromIO; const applyTask = (task) => M.ap(M.now(task), M.now(void 0)); /** * @since 0.9.2 * @category Instance */ export const FromTask = { ...FromIO, fromTask: flow(applyTask, M.awaitPromises), }; /** * @since 0.9.2 * @category Constructor */ export const fromTask = FromTask.fromTask; /** * @since 0.9.2 * @category Instance */ export const FromResume = { fromResume: (resume) => M.newStream((sink, scheduler) => { const run = () => pipe(resume, R.start((a) => { sink.event(scheduler.currentTime(), a); sink.end(scheduler.currentTime()); })); const onError = (error) => sink.error(scheduler.currentTime(), error); return asap(createCallbackTask(run, onError), scheduler); }), }; /** * @since 0.9.2 * @category Constructor */ export const fromResume = FromResume.fromResume; /** * @since 0.9.2 * @category Combinator */ export const chainFirstResumeK = FRe.chainFirstResumeK(FromResume, Chain); /** * @since 0.9.2 * @category Combinator */ export const chainResumeK = FRe.chainResumeK(FromResume, Chain); /** * @since 0.9.2 * @category Constructor */ export const fromResumeK = FRe.fromResumeK(FromResume); /** * @since 0.9.2 * @category Instance */ export const Alt = { ...Functor, alt: (f) => (fa) => M.take(1, M.merge(fa, f())), // race the 2 streams }; /** * @since 0.9.2 * @category Combinator */ export const race = Alt.alt; /** * @since 0.9.2 * @category Instance */ export const Alternative = { ...Alt, zero: M.empty, }; /** * @since 0.9.2 * @category Constructor */ export const zero = Alternative.zero; /** * @since 0.9.2 * @category Instance */ export const Compactable = { compact, separate, }; /** * @since 0.9.2 * @category Instance */ export const Filterable = { partitionMap, partition, filterMap, filter: M.filter, }; /** * @since 0.9.2 * @category Constructor */ export const Do = pipe(null, M.now, M.map(Object.create)); /** * @since 0.9.2 * @category Combinator */ export const bindTo = bindTo_(Functor); /** * @since 0.9.2 * @category Combinator */ export const tupled = tupled_(Functor); const emptySink = { event: constVoid, error: constVoid, end: constVoid, }; /** * @since 0.9.2 * @category Constructor */ export const createSink = (sink = {}) => ({ ...emptySink, ...sink, }); /** * @since 0.9.2 * @category Combinator */ export const collectEvents = (scheduler) => (stream) => { const events = []; return M.runEffects(M.tap((a) => events.push(a), stream), scheduler).then(() => events); }; /** * @since 0.9.2 * @category Combinator */ export const onDispose = (disposable) => (stream) => M.newStream((sink, scheduler) => disposeBoth(stream.run(sink, scheduler), disposable)); /** * @since 0.9.2 * @category Combinator */ export const combineAll = (...streams) => pipe(streams, M.combineArray(Array)); /** * @since 0.11.0 * @category Combinator */ export const combineStruct = (streams) => pipe(combineAll(...pipe(Object.entries(streams), RA.map(([k, stream]) => pipe(stream, M.map((v) => S.make(k, v)))))), M.map((o) => Object.assign({}, ...o))); /** * @since 0.9.2 * @category Combinator */ export const exhaustLatest = (stream) => new ExhaustLatest(stream); /** * @since 0.9.2 * @category Combinator */ export const exhaustMapLatest = (f) => (stream) => pipe(stream, M.map(f), exhaustLatest); class ExhaustLatest { constructor(stream) { this.stream = stream; } run(sink, scheduler) { const s = new ExhaustLatestSink(sink, scheduler); return disposeBoth(this.stream.run(s, scheduler), s); } } class ExhaustLatestSink { constructor(sink, scheduler) { this.sink = sink; this.scheduler = scheduler; this.latest = O.none; this.disposable = disposeNone(); this.sampling = false; this.shouldResample = false; this.finished = false; this.event = (t, stream) => { this.latest = O.some(stream); if (this.sampling) { this.shouldResample = true; return; } this.sampling = true; this.shouldResample = false; this.disposable = stream.run(this.innerSink, schedulerRelativeTo(t, this.scheduler)); }; this.error = (t, e) => { this.dispose(); this.sink.error(t, e); }; this.end = (_) => { this.finished = true; }; this.dispose = () => { this.disposable.dispose(); this.sampling = false; this.shouldResample = false; }; this.innerSink = { event: (t, a) => sink.event(t, a), error: (t, e) => this.error(t, e), end: (t) => { this.sampling = false; if (this.shouldResample && O.isSome(this.latest)) { this.event(scheduler.currentTime(), this.latest.value); return; } if (this.finished) { this.dispose(); sink.end(t); } }, }; } } /** * Using the provided Eq mergeMapWhen conditionally applies a Kliesli arrow * to the values within an Array when they are added and any values removed * from the array will be disposed of immediately * @since 0.9.2 * @category Combinator */ export const mergeMapWhen = (Eq = deepEqualsEq) => (f) => (values) => new MergeMapWhen(Eq, f, values); class MergeMapWhen { constructor(Eq, f, values) { this.Eq = Eq; this.f = f; this.values = values; } run(sink, scheduler) { const s = new MergeMapWhenSink(sink, scheduler, this); return disposeBoth(this.values.run(s, scheduler), s); } } class MergeMapWhenSink { constructor(sink, scheduler, source) { this.sink = sink; this.scheduler = scheduler; this.source = source; this.disposables = new Map(); this.values = new Map(); this.current = []; this.finished = false; this.referenceCount = 0; this.disposable = disposeNone(); this.event = (t, values) => { const removed = pipe(this.current, this.findDifference(values)); const added = pipe(values, this.findDifference(this.current)); this.current = values; // Clean up all the removed keys pipe(removed, RA.map(this.findDisposable), RA.compact, disposeAll).dispose(); removed.forEach((r) => this.values.delete(r)); // Subscribe to all the newly added values added.forEach((a) => this.disposables.set(a, this.runInner(t, a))); this.emitIfReady(); }; this.error = (t, error) => { this.dispose(); this.sink.error(t, error); }; this.end = (t) => { this.finished = true; this.endIfReady(t); }; this.dispose = () => { this.finished = true; this.disposables.forEach((d) => d.dispose()); this.disposables.clear(); this.values.clear(); this.current = []; }; this.runInner = (t, v) => this.source.f(v).run(this.innerSink(v), schedulerRelativeTo(t, this.scheduler)); this.innerSink = (v) => { this.referenceCount++; return { event: (_, a) => { this.values.set(v, a); this.emitIfReady(); }, error: (t, e) => this.error(t, e), end: (t) => { this.referenceCount--; this.endIfReady(t); }, }; }; this.emitIfReady = () => { const values = pipe(this.current, RA.map(this.findValue), RA.compact); if (values.length === this.current.length) { this.sink.event(this.scheduler.currentTime(), values); } }; this.endIfReady = (t) => { if (this.finished && this.referenceCount === 0) { this.sink.end(t); this.dispose(); } }; this.findDisposable = (k) => RM.lookup(source.Eq)(k)(this.disposables); this.findValue = (k) => RM.lookup(source.Eq)(k)(this.values); this.findDifference = RA.difference(source.Eq); } } /** * @since 0.9.2 * @category Combinator */ export const keyed = (Eq) => (stream) => new Keyed(Eq, stream); class Keyed { constructor(Eq, stream) { this.Eq = Eq; this.stream = stream; } run(sink, scheduler) { const s = new KeyedSink(this.Eq, sink, scheduler); return disposeBoth(s, this.stream.run(s, scheduler)); } } class KeyedSink { constructor(Eq, sink, scheduler) { this.Eq = Eq; this.sink = sink; this.scheduler = scheduler; this.adapters = new Map(); this.current = []; this.event = (t, values) => { // Clean up after any removed values const removed = pipe(this.current, this.findDifference(values)); removed.forEach((r) => { // Send the end signal this.getAdapter(r)[1][0](null); // Delete this.adapters.delete(r); }); this.current = values; // Emit our latest set of streams this.sink.event(t, values.map((a) => pipe(a, this.getAdapter, fst, snd))); values.forEach((a) => pipe(a, this.getAdapter, fst, fst)(a)); }; this.getAdapter = (k) => { return pipe(k, this.findAdapter, O.getOrElseW(() => { const endSignal = create(); const values = create(flow(M.startWith(k), M.until(endSignal[1]), M.multicast)); const adapter = [values, endSignal]; this.adapters.set(k, adapter); return adapter; })); }; this.error = (t, err) => { this.dispose(); this.sink.error(t, err); }; this.end = (t) => { this.dispose(); this.sink.end(t); }; this.dispose = () => { this.current = []; this.adapters.clear(); }; this.findDifference = RA.difference(Eq); this.findAdapter = (k) => RM.lookup(Eq)(k)(this.adapters); } } /** * @since 0.9.2 * @category Combinator */ export const switchFirst = (second) => (first) => pipe(first, M.map((a) => pipe(second, M.constant(a), M.startWith(a))), M.switchLatest); /** * @since 0.9.2 * @category Combinator */ export function mergeFirst(a) { return (b) => pipe(pipe(a, M.constant(O.none)), M.merge(pipe(b, M.map(O.some))), compact); } export * from '@most/core'; export * from '@most/types'; //# sourceMappingURL=Stream.js.map