Commit 6603b9c3 by Vladislav Lagunov

Удален eff

parent 681c0ec6
import { Eff } from './';
import Monad from 'burrido';
// Do notation
export const { Do } = Monad({
pure: Eff.of,
bind: (m, proj) => m.chain(proj),
});
Eff.Do = Do;
declare module "./index" {
interface EffStatics {
Do<Error, Success>(iter: () => IterableIterator<Eff<Error, Success>>): Eff<Error, Success>;
}
}
import * as either from '../either';
import { Either } from '../either';
import { absurd } from '../types';
// Alias
export type Cmd<Action> = Eff<never, Action>;
// ADT
export type Eff<Error, Success> =
| Pure<Error, Success> // { value: Either<Error, Success> }
| Thunk<Error, Success> // { args: unknown[], run(...args): Either<Error, Success> }
| Subscribe<Error, Success> // { subscribe(onNext: (x: Either<Error, Success>) => void, onComplete: () => void): () => void }
| Batch<Error, Success> // { steps: Eff<Error, Success>[] }
| Concat<Error, Success> // { steps: Eff<Error, Success>[] }
| Apply<Error, Success> // { args: Eff<unknown>[], proj(...args): Either<Error, Success> }
| Chain<Error, Success> // { first: Eff<unknown>, andThen(x: unknown): Eff<Error, Success> }
| HasEffect<Error, Success> // { toEffect(): Eff<Error, Success> }
;
export type AnyEff = Eff<any, any>;
// Instance methods for `Eff`
export class EffBase<Error, Success> {
readonly _Error: Error;
readonly _Success: Success;
map<Success2>(proj: (value: Success) => Success2): Eff<Error, Success2> {
return this.mapE(ethr => ethr.map(proj));
}
mapTo<Success2>(value: Success2): Eff<Error, Success2> {
return this.mapE(ethr => ethr.mapTo(value));
}
mapError<Error2>(proj: (value: Error) => Error2): Eff<Error2, Success> {
return this.mapE(ethr => ethr.mapLeft(proj));
}
mapErrorTo<Error2>(value: Error2): Eff<Error2, Success> {
return this.mapE(ethr => ethr.mapLeftTo(value));
}
mapE<Error2, Success2>(proj: (x: Either<Error, Success>) => Either<Error2, Success2>): Eff<Error2, Success2> {
// @ts-ignore
return new Apply([this], proj);
}
chain<Success2>(andThen: (x: Success) => Eff<Error, Success2>): Chain<Error, Success2>;
chain<Error2, Success2>(andThen: (x: Success) => Eff<Error2, Success2>): Chain<Error|Error2, Success2>;
chain<Error2, Success2>(andThen: (x: Success) => Eff<Error2, Success2>): Chain<Error|Error2, Success2> {
// @ts-ignore
return new Chain(this.toEff(), ethr => ethr.fold(failure, andThen));
}
chainTo<Success2>(value: Eff<Error, Success2>): Eff<Error, Success2>;
chainTo<Error2, Success2>(value: Eff<Error2, Success2>): Eff<Error|Error2, Success2>;
chainTo<Error2, Success2>(value: Eff<Error2, Success2>): Eff<Error|Error2, Success2> {
return this.chain(() => value);
}
chainE<Success2>(andThen: (x: Either<Error, Success>) => Eff<Error, Success2>): Eff<Error, Success2>;
chainE<Error2, Success2>(andThen: (x: Either<Error, Success>) => Eff<Error2, Success2>): Eff<Error|Error2, Success2>;
chainE<Error2, Success2>(andThen: (x: Either<Error, Success>) => Eff<Error2, Success2>): Eff<Error|Error2, Success2> {
// @ts-ignore
return new Chain(this.toEff(), andThen);
}
run(onNext: (x: Either<Error, Success>) => void, onComplete: () => void): () => void {
return go(this.toEff(), onNext, onComplete);
}
subscribe(onNext: (x: Either<Error, Success>) => void, onComplete: () => void): () => void {
return go(this.toEff(), onNext, onComplete);
}
toEff() {
return this as any as Eff<Error, Success>;
}
}
export function of<A>(value: A): Pure<never, A> {
return new Pure(either.success(value));
}
export function success<A>(value: A): Pure<never, A> {
return new Pure(either.success(value));
}
export function failure<A>(value: A): Pure<A, never> {
return new Pure(either.failure(value));
}
export function fromCallback<L, R>(run: (onNext: (x: Either<L, R>) => void, onComplete: () => void) => () => void): Eff<L, R> {
return new Subscribe(run);
}
export function fromPromise<L, R, Args extends unknown[]>(func: (...args: Args) => Promise<Either<L, R>>, ...args: Args): Eff<L, R> {
return new Subscribe((onNext, onComplete) => {
func(...args).then(x => (onNext(x), onComplete()));
return noopFunc;
});
}
export function fromPromise_<L, R>(promise: Promise<R>): Eff<unknown, R> {
return new Subscribe((onNext, onComplete) => {
promise.then(x => (onNext(either.success(x)), onComplete()), e => (onNext(either.failure(e)), onComplete()));
return noopFunc;
});
}
export function fromEither<L, R>(value: Either<L, R>): Eff<L, R> {
return new Pure(value);
}
export function thunk<L, A, Args extends unknown[]>(run: (...args: Args) => Either<L,A>, ...args: Args): Eff<L, A> {
return new Thunk(run as any, args);
}
export { thunk as lazy }; // DEPRECATED
export function ap<L,A,B>(a: Eff<L,A>, f: (a: A) => B): Eff<L,B>;
export function ap<L,A,B,C>(a: Eff<L,A>, b: Eff<L,B>, f: (a: A, b: B) => C): Eff<L,C>;
export function ap<L,A,B,C,D>(a: Eff<L,A>, b: Eff<L,B>, c: Eff<L,C>, f: (a: A, b: B, c: C) => D): Eff<L,D>;
export function ap<L,A,B,C,D,E>(a: Eff<L,A>, b: Eff<L,B>, c: Eff<L,C>, d: Eff<L,D>, f: (a: A, b: B, c: C, d: D) => E): Eff<L,E>;
export function ap<L,A,B,C,D,E,F>(a: Eff<L,A>, b: Eff<L,B>, c: Eff<L,C>, d: Eff<L,D>, e: Eff<L,E>, f: (a: A, b: B, c: C, d: D, e: E) => F): Eff<L,F>;
export function ap<L,A,B,C,D,E,F,G>(a: Eff<L,A>, b: Eff<L,B>, c: Eff<L,C>, d: Eff<L,D>, e: Eff<L,E>, f_: Eff<L,F>, f: (a: A, b: B, c: C, d: D, e: E, f: F) => G): Eff<L,G>;
export function ap(): Eff<unknown, unknown> {
const args = Array.prototype.slice.call(arguments, 0, arguments.length - 1);
const proj = arguments[arguments.length - 1];
return new Apply(args, proj);
}
export function record<R extends Record<string, AnyEff>>(rec: R): Eff<{ [K in keyof R]: R[K]['_Error'] }[keyof R], { [K in keyof R]: R[K]['_Success'] }> {
const keys = Object.keys(rec);
return ap.apply(undefined, [...keys.map(k => rec[k]), (...values) => values.reduce((acc, v, idx) => (acc[keys[idx]] = v, acc), {})]);
}
/** Traverse an array */
export function traverse<Error, A, B>(array: A[], f: (a: A, idx: number) => Eff<Error, B>): Eff<Error, B[]> {
if (array.length === 0) return success<B[]>([]);
return ap.apply(undefined, [...array.map(f), (...args) => args]);
}
/**
* Объединение нескольких параллельно выполняемых `Cmd`
*/
export function batch<Steps extends Cmd<unknown>[]>(...steps: Steps): Cmd<Steps[number]['_Success']>;
export function batch<Steps extends Cmd<unknown>[]>(steps: Steps): Cmd<Steps[number]['_Success']>;
export function batch(): Cmd<unknown> {
const steps = Array.isArray(arguments[0]) ? arguments[0] : arguments;
return new Batch(steps);
}
/**
* Объединение нескольких `Cmd` в очередь
*/
export function concat<Steps extends Cmd<unknown>[]>(...steps: Steps): Cmd<Steps[number]['_Success']>;
export function concat<Steps extends Cmd<unknown>[]>(steps: Steps): Cmd<Steps[number]['_Success']>;
export function concat(): Cmd<unknown> {
const steps = Array.isArray(arguments[0]) ? arguments[0] : arguments;
return new Batch(steps);
}
/**
* Выполнение сайд-еффектов. Observable не генерирует событий, после
* запуска вызывается переданная функция и Observable завершается.
*/
export function forget<Args extends unknown[]>(run: (...args: Args) => unknown, ...args: Args): Cmd<never> {
return new Subscribe((onNext, onComplete) => {
run(...args);
onComplete();
return noopFunc;
});
}
// Functional helpers
const noopFunc = () => {};
// Perform side effects
export function go<Error, Success>(effect: Eff<Error, Success>, onNext: (x: Either<Error, Success>) => void, onComplete: () => void): () => void {
// @ts-ignore
const _this = this;
if (effect instanceof Pure) {
onNext(effect.value);
onComplete();
return noopFunc;
}
if (effect instanceof Thunk) {
onNext(effect._run.apply(_this, effect.args));
onComplete();
return noopFunc;
}
if (effect instanceof Subscribe) {
return effect.subscribe(onNext, onComplete);
}
if (effect instanceof Batch) {
if (effect.steps.length === 0) { onComplete(); return noopFunc; }
let subscriptions: Array<Function|null>;
const loop = idx => () => {
subscriptions[idx] = null;
for (const unsub of subscriptions) if (unsub !== null) return;
onComplete(); // If control flow reaches here, that means all nested commands are completed
};
subscriptions = effect.steps.map((eff, idx) => go(eff, onNext, loop(idx)));
return () => subscriptions.forEach(
funOrNull => funOrNull ? funOrNull() : void 0
);
}
if (effect instanceof Concat) {
let unsubscribe: Function|null = null;
const loop = idx => () => {
// If condition holds, then all nested effects are completed, therefore we're done
if (idx >= effect.steps.length) { onComplete(); return; }
unsubscribe = go(effect.steps[idx], onNext, loop(idx + 1));
};
loop(0);
return () => unsubscribe ? unsubscribe() : void 0;
}
if (effect instanceof Chain) {
const cancellers = new Map<AnyEff, Canceller>();
const handleEffect = (e: AnyEff) => {
const _onNext = result => {
if (e === effect.first) handleEffect(effect.andThen(result));
else onNext(result);
};
const _onComplete = () => {
if (!cancellers.has(e)) completedImmediately = true;
cancellers.delete(e);
if (cancellers.size === 0) onComplete();
};
let completedImmediately = false;
const canceller = go(e, _onNext, _onComplete);
if (!completedImmediately) cancellers.set(e, canceller);
};
handleEffect(effect.first);
if (cancellers.size === 0) return noopFunc;
return () => cancellers.forEach(canceller => canceller());
}
if (effect instanceof Apply) {
let allInitialized = false;
let subscriptions: Array<Function|undefined|null> = new Array(effect.args.length);
const initializedFlags: Array<true|undefined> = new Array(effect.args.length);
const recentValues: unknown[] = new Array(effect.args.length);
const next = idx => result => {
recentValues[idx] = result;
check_initialized: {
if (allInitialized) break check_initialized;
initializedFlags[idx] = true;
for (const flag of initializedFlags) if (flag !== true) return;
allInitialized = true;
}
onNext(effect.proj.apply(_this, recentValues));
};
const complete = idx => () => {
subscriptions[idx] = null;
for (const unsub of subscriptions) if (unsub !== null) return;
onComplete();
};
effect.args.forEach((eff, idx) => {
const canceller = go(eff, next(idx), complete(idx));
if (subscriptions[idx] !== null) subscriptions[idx] = canceller;
});
return () => subscriptions.forEach(
funOrNull => funOrNull ? funOrNull() : void 0
);
}
if (effect instanceof HasEffect) {
return go(effect.toEffect(), onNext, onComplete);
}
return absurd(effect);
}
export class Pure<Error, Success> extends EffBase<Error, Success> {
constructor(
readonly value: Either<Error, Success>,
) { super(); }
}
export class Thunk<Error, Success> extends EffBase<Error, Success> {
constructor(
readonly _run: (...args: unknown[]) => Either<Error, Success>,
readonly args: unknown[],
) { super(); }
}
export class Subscribe<Error, Success> extends EffBase<Error, Success> {
constructor(
readonly subscribe: (onNext: (x: Either<Error, Success>) => void, onComplete: () => void) => () => void,
) { super(); }
}
export class Batch<Error, Success> extends EffBase<Error, Success> {
constructor(
readonly steps: Eff<Error, Success>[],
) { super(); }
}
export class Concat<Error, Success> extends EffBase<Error, Success> {
constructor(
readonly steps: Eff<Error, Success>[],
) { super(); }
}
export class Apply<Error, Success> extends EffBase<Error, Success> {
constructor(
readonly args: Eff<Error, unknown>[],
readonly proj: (...args) => Either<Error, Success>
) { super(); }
}
export class Chain<Error, Success> extends EffBase<Error, Success> {
constructor(
readonly first: Eff<Error, unknown>,
readonly andThen: (x: Either<Error, unknown>) => Eff<Error, Success>
) { super(); }
}
export abstract class HasEffect<Error, Success> extends EffBase<Error, Success> {
abstract toEffect(): Eff<Error, Success>;
}
/**
* Примитивный `Cmd` не генерирует никаких действий, завершается сразу
* после запуска.
*/
export const noop: Cmd<never> = new Batch([]);
export type Canceller = () => void;
export interface EffStatics {
of: typeof of,
success: typeof success,
failure: typeof failure,
fromCallback: typeof fromCallback,
fromPromise: typeof fromPromise,
fromPromise_: typeof fromPromise_,
fromEither: typeof fromEither,
thunk: typeof thunk,
lazy: typeof thunk,
ap: typeof ap,
record: typeof record,
batch: typeof batch,
concat: typeof concat,
forget: typeof forget,
go: typeof go,
noop: typeof noop,
}
export const Eff = {
of,
success,
failure,
fromCallback,
fromPromise,
fromPromise_,
fromEither,
thunk,
lazy: thunk,
ap,
record,
batch,
concat,
forget,
go,
noop,
} as EffStatics;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment