ts-prime
Version:
A utility library for JavaScript and Typescript.
196 lines (183 loc) • 4.27 kB
text/typescript
import { LazyResult } from './_reduceLazy';
/**
* Perform left-to-right function composition.
* @param value The initial value.
* @param operations the list of operations to apply.
* @signature P.pipe(data, op1, op2, op3)
* @example
* P.pipe(
* [1, 2, 3, 4],
* P.map(x => x * 2),
* arr => [arr[0] + arr[1], arr[2] + arr[3]],
* ) // => [6, 14]
*
*
* @data_first
* @category Function
*/
export function pipe<A, B>(value: A, op1: (input: A) => B): B;
export function pipe<A, B, C>(
value: A,
op1: (input: A) => B,
op2: (input: B) => C
): C;
export function pipe<A, B, C, D>(
value: A,
op1: (input: A) => B,
op2: (input: B) => C,
op3: (input: C) => D
): D;
export function pipe<A, B, C, D, E>(
value: A,
op1: (input: A) => B,
op2: (input: B) => C,
op3: (input: C) => D,
op4: (input: D) => E
): E;
export function pipe<A, B, C, D, E, F>(
value: A,
op1: (input: A) => B,
op2: (input: B) => C,
op3: (input: C) => D,
op4: (input: D) => E,
op5: (input: E) => F
): F;
export function pipe<A, B, C, D, E, F, G>(
value: A,
op1: (input: A) => B,
op2: (input: B) => C,
op3: (input: C) => D,
op4: (input: D) => E,
op5: (input: E) => F,
op6: (input: F) => G
): G;
export function pipe<A, B, C, D, E, F, G, H>(
value: A,
op1: (input: A) => B,
op2: (input: B) => C,
op3: (input: C) => D,
op4: (input: D) => E,
op5: (input: E) => F,
op6: (input: F) => G,
op7: (input: G) => H
): H;
export function pipe(
value: any,
...operations: Array<(value: any) => any>
): any {
let ret = value;
const lazyOps = operations.map(op => {
const { lazy, lazyArgs } = op as LazyOp;
if (lazy) {
const fn: any = lazy(...lazyArgs);
fn.indexed = lazy.indexed;
fn.single = lazy.single;
fn.index = 0;
fn.items = [];
return fn;
}
return null;
});
let opIdx = 0;
while (opIdx < operations.length) {
const op = operations[opIdx];
const lazyOp = lazyOps[opIdx];
if (!lazyOp) {
ret = op(ret);
opIdx++;
continue;
}
const lazySeq: LazyFn[] = [];
for (let j = opIdx; j < operations.length; j++) {
if (lazyOps[j]) {
lazySeq.push(lazyOps[j]);
if (lazyOps[j].single) {
break;
}
} else {
break;
}
}
let acc: any[] = [];
for (let j = 0; j < ret.length; j++) {
let item = ret[j];
if (_processItem({ item, acc, lazySeq })) {
break;
}
}
const lastLazySeq = lazySeq[lazySeq.length - 1];
if ((lastLazySeq as any).single) {
ret = acc[0];
} else {
ret = acc;
}
opIdx += lazySeq.length;
}
return ret;
}
type LazyFn = (value: any, index?: number, items?: any) => LazyResult<any>;
type LazyOp = ((input: any) => any) & {
lazy: ((...args: any[]) => LazyFn) & {
indexed: boolean;
single: boolean;
};
lazyArgs: any[];
};
function _processItem({
item,
lazySeq,
acc,
}: {
item: any;
lazySeq: any[];
acc: any[];
}): boolean {
if (lazySeq.length === 0) {
acc.push(item);
return false;
}
let lazyResult: LazyResult<any> = { done: false, hasNext: false };
let isDone = false;
for (let i = 0; i < lazySeq.length; i++) {
const lazyFn = lazySeq[i];
const indexed = lazyFn.indexed;
const index = lazyFn.index;
const items = lazyFn.items;
items.push(item);
lazyResult = indexed ? lazyFn(item, index, items) : lazyFn(item);
lazyFn.index++;
if (lazyResult.hasNext) {
if (lazyResult.hasMany) {
const nextValues: any[] = lazyResult.next;
for (const subItem of nextValues) {
const subResult = _processItem({
item: subItem,
acc,
lazySeq: lazySeq.slice(i + 1),
});
if (subResult) {
return true;
}
}
return false;
} else {
item = lazyResult.next;
}
}
if (!lazyResult.hasNext) {
break;
}
// process remaining functions in the pipe
// but don't process remaining elements in the input array
if (lazyResult.done) {
isDone = true;
}
}
if (lazyResult.hasNext) {
acc.push(item);
}
if (isDone) {
return true;
}
return false;
}