Skip to content

Commit

Permalink
Refactor pipe method to handle operations with arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
shtse8 committed Mar 17, 2024
1 parent 22597fe commit af7e56d
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 69 deletions.
41 changes: 6 additions & 35 deletions src/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,19 @@
export class Chain<I, O> {
constructor(private initialValue: I, private ops: ((value: any) => any)[] = []) { }

pipe<U>(fn: (value: O) => U): Chain<I, U> {
return new Chain<I, U>(this.initialValue, [...this.ops, fn]);
pipe<U>(fn: (value: O) => U): Chain<I, U>;
pipe<U, Args extends unknown[]>(fn: (value: O, ...args: Args) => U, builder: (x: (...args: Args) => void) => void): Chain<I, U>;
pipe<U, Args extends unknown[]>(fn: (value: O, ...args: Args) => U, builder?: (x: (...args: Args) => void) => void): Chain<I, U> {
let thisArgs: Args = [] as unknown as Args;
builder?.((...args: Args) => thisArgs = args);
return new Chain<I, U>(this.initialValue, [...this.ops, (value: O) => fn(value, ...thisArgs)]);
}

value(): O {
return this.ops.reduce((acc, fn) => fn(acc), this.initialValue) as unknown as O;
}
}

/**
* Converts a function into a format suitable for use with the `Chain.pipe` method.
* This allows for the inclusion of additional parameters to the function prior to its
* execution in the chain. `$op` effectively curries the provided function, splitting
* its application into two steps: first, `$op` is called with the function to adapt,
* returning a new function. This returned function is then called with the additional
* arguments needed for the adapted function, and it returns yet another function that
* expects the current value in the chain as its input. This final function is suitable
* for use with `Chain.pipe`, as it matches the expected signature by taking a single
* argument—the current value in the chain—and applying the original function to it,
* along with the pre-specified additional arguments.
*
* @param fn The function to be converted. This function can take multiple parameters,
* with the first parameter intended to be the current value in the chain, and the
* rest being additional arguments provided during the subsequent call.
* @returns A function that, when called with additional arguments, returns a new
* function designed for the `Chain.pipe` method. This new function takes the current
* value in the chain as its sole argument and applies the original function with the
* specified additional arguments.
*
* @example
* // Assuming `add` and `multiply` are defined as:
* // const add = (x, y) => x + y;
* // const multiply = (x, y) => x * y;
*
* // Correct usage in a chain
* chain(5).pipe($op(add)(5)).pipe($op(multiply)(2)).value(); // returns 20
*/
export function $op<T, Args extends unknown[], U>(fn: (value: T, ...args: Args) => U) {
return (...args: Args) => (value: T) => fn(value, ...args);
}


/**
* Chains a value to be used in a pipeline.
* @param value value to chain
Expand Down
44 changes: 10 additions & 34 deletions tests/chain.test.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,9 @@
import { describe, test, it, expect } from 'bun:test'
import { $op, chain } from '../src/index'
import { chain } from '../src/index'

// Define a simple function for testing
const add = (x: number, y: number): number => x + y;

describe('$op function', () => {
it('correctly curries a function with provided arguments', () => {
// Curry the `add` function with $op
const curriedAdd = $op(add);

// Prepare the curried function with one argument (5)
const addFive = curriedAdd(5);

// Now `addFive` should be a function that expects another number and adds 5 to it
const result = addFive(10); // This should be equivalent to `add(10, 5)`

// Verify the result is as expected
expect(result).toBe(15);
});

it('returns a function that correctly applies all arguments to the original function', () => {
// Define a more complex function for testing
const subtract = (x: number, y: number, z: number): number => x - y - z;

// Curry the `subtract` function with $op
const curriedSubtract = $op(subtract);

// Prepare the curried function with two arguments (10, 5)
const subtractTenAndFive = curriedSubtract(10, 5);

// Now `subtractTenAndFive` should be a function that expects another number,
// subtracts 10 and then 5 from it
const result = subtractTenAndFive(20); // This should be equivalent to `subtract(20, 10, 5)`

// Verify the result is as expected
expect(result).toBe(5);
});
});

describe('Chain class', () => {
it('initializes with a value and unwraps it correctly', () => {
Expand Down Expand Up @@ -73,4 +40,13 @@ describe('Chain class', () => {
// Expected result is a string transformation of the initial value
expect(result).toBe("Hello 5");
});

it('handles operations with arguments', () => {
const initialValue = 5;
const add = (x: number, y: number) => x + y;
const result = chain(initialValue).pipe(add, x => x(5)).value();

expect(result).toBe(10);
});

});

0 comments on commit af7e56d

Please sign in to comment.