data.task.ts
Version:
Personal functional Task typeclass implementation
299 lines • 12.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.Task = void 0;
const monet_1 = require("monet");
const synchronize = (rej, res) => {
let fn = null;
let val = null;
let rejected = false;
const rejecter = (err) => {
if (rejected)
return;
rejected = true;
rej(err);
};
const resolver = (setter) => (x) => {
if (rejected)
return;
setter(x);
if (fn != null && val != null)
res(fn(val));
};
return {
guardReject: rejecter,
resolveFn: resolver((f) => {
fn = f;
}),
resolveValue: resolver((v) => {
val = v;
}),
};
};
const taskToTaskOfEither = (t) => new Task((_, res) => {
t.fork(err => res(monet_1.Either.Left(err)), succ => res(monet_1.Either.Right(succ)));
});
/**
* The `Task<E, R>` structure represents values that depend on time. This
* allows one to model time-based effects explicitly, such that one can have
* full knowledge of when they're dealing with delayed computations, latency,
* or anything that can not be computed immediately.
*
* A common use for this structure is to replace the usual Continuation-Passing
* Style form of programming, in order to be able to compose and sequence
* time-dependent effects using the generic and powerful monadic operations.
*
* @class
* @summary
* ((α → Void), (β → Void) → Void)) → Task<E, R>
*
* Task<E, R> <: Monad[β]
* , Functor[β]
* , Applicative[β]
*/
class Task {
/**
* Represents a lazy asyncronous computation a Task
* @constructor
* @param {(reject: (error: E) => void, resolve: (value: R) => void) => void} fork - Callback with resolvers for failure and success
*/
constructor(fork) {
this.fork = fork;
/**
* Chain function
* @alias chain
*/
this.bind = this.chain.bind(this);
/**
* Chain function
* @alias chain
*/
this.flatMap = this.chain.bind(this);
/**
* Cata function
* @alias fold
*/
this.cata = this.fold.bind(this);
}
/**
* Pointed constructor of a Task similar to Promise.resolve(any)
* @param {R} r - value to resolve
* @returns {Task<E, R>} the Task
*/
static of(r) {
return new Task((_, resolve) => resolve(r));
}
/**
* Returns a rejected Task similar to Promise.reject(any)
* @param {E} err - Error to reject
* @returns {Task<E, R>} the rejected Task
*/
static rejected(err) {
return new Task(reject => reject(err));
}
/**
* Transforms a promise to a Task, take into account that this will run the Promise because of its eager nature
* @param {Promise<R>} p - Promise to convert to a Task
* @returns {Task<E, R>} the Task from the Promise
*/
static fromPromise(p) {
return new Task((reject, resolve) => p.then(resolve).catch(reject));
}
/**
* Transforms a function that returns a promise to a Task, in this case the promise will be run lazily
* @param {() => Promise<R>} p - Promise to convert to a Task
* @returns {Task<E, R>} the Task from the Promise
*/
static fromLazyPromise(lazyPromise) {
return new Task((reject, resolve) => lazyPromise().then(resolve).catch(reject));
}
/**
* Applies natural transformation from Either to Task
* @param {Either<E, R>} e - Either kind from monet
* @returns {Task<E, R>} the Task from the Either provided
*/
static fromEither(e) {
return new Task((reject, resolve) => e.fold(reject, resolve));
}
/**
* Transforms a function that throws to Task
* @param {() => R} f - Function that returns R and might throw E
* @returns {Task<E, R>} the Task
*/
static fromTry(f) {
return new Task((reject, resolve) => {
try {
return resolve(f());
}
catch (error) {
return reject(error);
}
});
}
/**
* Traverses an array in applicative way transforming every element into a task and running them in parallel
* @param {(A) => Task<E, B>} f - Function maps an item to a Task
* @param {Array<A>} arr - array of elements to be traversed
* @returns {Task<E, Array<B>>} a Task containing an array of transformed elements
*/
static arrayTraverseA(f, arr) {
const cons = (x) => (xs) => xs.concat([x]);
return arr.reduce((tl, i) => Task.of(cons).apTo(f(i)).apTo(tl), Task.of([]));
}
/**
* Traverses an array in a monadic way transforming every element into a task and running them in sequence
* @param {(A) => Task<E, B>} f - Function maps an item to a Task
* @param {Array<A>} arr - array of elements to be traversed
* @returns {Task<E, Array<B>>} a Task containing an array of transformed elements
*/
static arrayTraverseM(f, arr) {
return arr.reduce((tl, i) => tl.chain(xs => f(i).chain(x => Task.of(xs.concat(x)))), Task.of([]));
}
/**
* Mimics Promise.all() behaviour but runs sequentially a list of Tasks, fails when some one fails
* @param {[Task<E, R>, Task<E1, R1>, ... , Task<EN, RN>]} arr - Array of Tasks to traverse
* @returns {Task<E | E2 | ... | EN, [R, R1, ... , RN]>} Task of array of resolved Tasks
*/
static allSeq(arr) {
return arr.reduce((acc, x) => acc.chain((listOfT) => x.map((t) => [...listOfT, t])), Task.of([]));
}
/**
* Mimics Promise.all() behaviour and runs in parallel a list of tasks, fails when some one fails
* @param {[Task<E, R>, Task<E1, R1>, ... , Task<EN, RN>]} arr - Array of Tasks to traverse
* @returns {Task<E | E2 | ... | EN, [R, R1, ... , RN]>} Task of array of resolved Tasks
*/
static all(arr) {
return arr.reduce((acc, tv) => tv.chain((el) => Task.of((list) => [...list, el])).apTo(acc), Task.of([]));
}
/**
* Mimics Promise.allSeatle() behaviour and runs sequentially a list of Tasks, never fails
* @param {[Task<E, R>, Task<E1, R1>, ... , Task<EN, RN>]} arr - Array of Tasks to traverse
* @returns {Task<never, [Either<E, R>, Either<E1, R1>, ... , Either<EN, RN>]>} Task of array of resolved Tasks
*/
static allSeattleSeq(arr) {
return arr
.map(taskToTaskOfEither)
.reduce((acc, x) => x.chain((t) => acc.map(listOfT => [...listOfT, t])), Task.of([]));
}
/**
* Mimics Promise.allSeatle() behaviour and runs in parallel a list of Tasks, never fails
* @param {[Task<E, R>, Task<E1, R1>, ... , Task<EN, RN>]} arr - Array of Tasks to traverse
* @returns {Task<never, [Either<E, R>, Either<E1, R1>, ... , Either<EN, RN>]>} Task of array of resolved Tasks
*/
static allSeattle(arr) {
return arr
.map(taskToTaskOfEither)
.reduce((acc, tv) => acc.chain((list) => Task.of((el) => [...list, el])).apTo(tv), Task.of([]));
}
/**
* Adapts a function that returns a Task<Error, Success> to bypass Success and continue composition should be use with chain
* @param {((input: S) => Task<E, void>) | ((input: S) => void)} f - Function that returns a Task<E, void>
* @param {S} success - Success from the previous function
* @returns A Task<Error, Success> of the same output Success
*/
static tap(f) {
return input => {
const r = f(input);
return r instanceof Task ? r.map(() => input) : Task.of(input);
};
}
/**
* Adapts a function that returns a Task<Error, Success> to bypass error and continue composition should be use with orElse
* @param {((input: E) => Task<void, S>) | ((input: E) => void)} f - Function that returns a Task<void, Error>
* @param {E} failure - Failure of the previous function
* @returns A Task<Error, Success> of the same failure Error
*/
static rejectTap(f) {
return failure => {
const r = f(failure);
return r instanceof Task ? r.rejectMap(() => failure) : Task.rejected(failure);
};
}
/**
* Applys the successful value of the `Task<E, R>` to the successful
* value of the `Task<E, (R → O)>`
* @param {Task<E, (r: R) => O>} to - Task holding an `(R → O)` on the success path
* @returns {Task<E, O>} the Task with the value applied
*/
ap(tf) {
return new Task((reject, resolve) => {
const { guardReject, resolveFn, resolveValue } = synchronize(reject, resolve);
this.fork(guardReject, resolveValue);
tf.fork(guardReject, resolveFn);
});
}
/**
* Applys the successful value of the `Task<E, (R → O)>` to the successful
* value of the `Task<E, R>`
* @param {Task<E, O>} to - Task holding an `O` on the success path
* @returns {Task<E, O>} the Task with the value applied
*/
apTo(to) {
return new Task((reject, resolve) => {
const { guardReject, resolveFn, resolveValue } = synchronize(reject, resolve);
to.fork(guardReject, resolveValue);
this.fork(guardReject, resolveFn);
});
}
/**
* Applies the function [f] in the success path of resolution of the Task. Functor interface implementation
* @param {(r: R) => O} f - Function that transfroms from type R to type O
* @returns {Task<E, O>} A Task containing an element of type O
*/
map(f) {
return new Task((reject, resolve) => this.fork((err) => reject(err), (r) => resolve(f(r))));
}
/**
* Applies the function [g] in the failure path of resolution of the Task
* @param {(e: E) => F} g - Function that transfroms from type E to type F
* @returns {Task<F, R>} A Task containing an element of type R but a failure of type F
*/
rejectMap(g) {
return new Task((reject, resolve) => this.fork((err) => reject(g(err)), (x) => resolve(x)));
}
/**
* Applies the function [f] in the success path of resolution of the Task. Monad interface implementation
* @param {(r: R) => Task<E, O>} f - Function that transfroms from type R to type O
* @returns {Task<E, O>} A Task containing an element of type O
*/
chain(f) {
return new Task((reject, resolve) => this.fork((err) => reject(err), (r) => f(r).fork(reject, resolve)));
}
/**
* Applies the function [g] in the failure path of resolution of the Task
* @param {(e: E) => Task<F, R>} g - Function that transfroms from type E to type F
* @returns {Task<F, R>} A Task containing an element of type R or failure of type F
*/
orElse(g) {
return new Task((reject, resolve) => this.fork((err) => g(err).fork(reject, resolve), (r) => resolve(r)));
}
/**
* Maps over both sides of execution applying [f] over success path or [g] over failure path
* @param {(r: R) => Task<E, O>} g - Function that transfroms from type R to type O
* @param {(e: E) => Task<F, R>} f - Function that transfroms from type E to type F
* @returns A Task containing a failure of type F or a success of type O
*/
bimap(g, f) {
return new Task((reject, resolve) => this.fork((e) => reject(g(e)), (r) => resolve(f(r))));
}
/**
* Catamorphism. Takes two functions [f] and [g], applies the leftmost one to the failure
* value, and the rightmost one to the successful value, depending on which one
* is present.
* @param {(r: R) => Task<E, O>} f - Function that transfroms from type R to type O
* @param {(e: E) => Task<F, R>} g - Function that transfroms from type E to type F
* @returns A Task containing a failure of type F or a success of type O
*/
fold(g, f) {
return new Task((_, resolve) => this.fork((e) => resolve(g(e)), (r) => resolve(f(r))));
}
/**
* Natural transformation to Promise
* You don't know when this could come in handy
* @returns {Promise<R>} a Promise equivalent
*/
toPromise() {
return new Promise((resolve, reject) => this.fork(reject, resolve));
}
}
exports.Task = Task;
//# sourceMappingURL=task.js.map
;