Skip to content

Instantly share code, notes, and snippets.

@waldogit
Last active April 18, 2016 20:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save waldogit/0e759d2dced95d014837a6edb099d5ea to your computer and use it in GitHub Desktop.
Save waldogit/0e759d2dced95d014837a6edb099d5ea to your computer and use it in GitHub Desktop.
variadic curry supporting 1 to 4 arguments, curried currying and partial application, in Typescript
const concatter4 = (a:string, b:string , c:string , d: string) => `a: ${a} b: ${b} c: ${c} d: ${d}`;
it('expect _ to allow partially applying an N4 curried function', () => {
expect(curry(concatter4)(_, 'b','c','d')('a')).toEqual('a: a b: b c: c d: d');
});
it('expect _, _ to allow partially applying an N4 curried function', () => {
expect(curry(concatter4)(_, _,'c','d')('a')('b')).toEqual('a: a b: b c: c d: d');
});
it('expect _, x, _ to allow partially applying an N4 curried function', () => {
expect(curry(concatter4)(_, 'b', _,'d')('a', 'c')).toEqual('a: a b: b c: c d: d');
});
it('expect _, x, _ with _ to allow partially applying an N4 curried function', () => {
expect(curry(concatter4)(_, 'b', _,'d')(_, 'c')('a')).toEqual('a: a b: b c: c d: d');
});
it('expect _, x, _ to allow repeatedly partially applying an N4 curried function', () => {
expect(curry(concatter4)(_, 'b', _,'d')('a')('c')).toEqual('a: a b: b c: c d: d');
});
it('expect _, _, _ to allow partially applying an N4 curried function', () => {
expect(curry(concatter4)(_, _, _,'d')('a')('b')('c')).toEqual('a: a b: b c: c d: d');
});
it('expect _, _,_ with _, _ to allow partially applying an N4 curried function', () => {
expect(curry(concatter4)(_, _, _,'d')(_, _,'c')('a', 'b')).toEqual('a: a b: b c: c d: d');
});
it('expect _, _, _ with _, _ with _ to allow partially applying an N4 curried function', () => {
expect(curry(concatter4)(_, _, _,'d')(_, _,'c')(_, 'b')('a')).toEqual('a: a b: b c: c d: d');
});
const concatter3 = (a:string, b:string , c:string) => `a: ${a} b: ${b} c: ${c}`;
it('expect 1st _ to allow partially applying an N3 curried function', () => {
expect(curry(concatter3)('a', _, 'c')('b')).toEqual('a: a b: b c: c');
});
it('expect 2d _ to allow partially applying an N3 curried function', () => {
expect(curry(concatter3)(_, 'b', 'c')('a')).toEqual('a: a b: b c: c');
});
it('expect _, _ to allow partially applying an N3 curried function', () => {
expect(curry(concatter3)(_, _, 'c')('a', 'b')).toEqual('a: a b: b c: c');
});
it('expect _, _ to allow repeatedly partially applying an N3 curried function', () => {
expect(curry(concatter3)(_, _, 'c')('a')('b')).toEqual('a: a b: b c: c');
});
it('expect _, _ with _ to allow partially applying an N3 curried function', () => {
expect(curry(concatter3)(_, _, 'c')(_, 'b')('a')).toEqual('a: a b: b c: c');
});
const concatter2 = (a:string, b:string ) => `a: ${a} b: ${b}`;
it('expect _ to allow partially applying an N2 curried function', () => {
expect(curry(concatter2)(_,'b')('a')).toEqual('a: a b: b');
});
const add1 = curry((a: number) => a + 10);
it('expect curry to handle 1 arg', () => {
expect(add1(32)).toEqual(42);
});
const curriedAdd = curry((a:string, b:number, c:number, d:number) => parseInt(a) + b + c + d);
it('expect curried curry4 to handle 4 + 0 args', () => {
expect(curry(curriedAdd)('1', 2, 3, 4)).toEqual(10);
// TS will not allow to compile:
// expect(curry(curriedAdd)(1, 2, 3, 4)).toEqual(10);
// expect(curry(curriedAdd)('1', '2', 3, 4)).toEqual(10);
// expect(curry(curriedAdd)(1)(2)(3)(4)).toEqual(10);
// expect(curry(curriedAdd)('1')(2)(3)('4')).toEqual(10);
});
it('expect curried curry4 to handle 2 + 1 + 1 args', () => {
expect(curry(curriedAdd)('1', 2)(3)(4)).toEqual(10);
});
it('expect curried curry4 to handle 1 + 1 + 1 + 1 args', () => {
expect(curry(curriedAdd)('1')(2)(3)(4)).toEqual(10);
});
const addp4 = curry((a: number, b: number, c: number, d: number) => a + b + c + d);
it('expect curry4 to handle partially completed _ 2 3 4 args', () => {
const partialAddp4 = addp4(_, 2, 3, 4);
expect(addp4(_, 2, 3, 4)(1)).toEqual(10);
});
const add4 = curry((a: number, b: number, c: number, d: number) => a + b + c + d);
it('expect curry4 to handle 4 + 0 args', () => {
expect(add4(1, 2, 3, 4)).toEqual(10);
});
it('expect curry4 to handle 3 + 1 args', () => {
expect(add4(1, 2, 3)(4)).toEqual(10);
});
it('expect curry4 to handle 2 + 2 args', () => {
expect(add4(1, 2)(3, 4)).toEqual(10);
});
it('expect curry4 to handle 1 + 3 args', () => {
expect(add4(1)(2, 3, 4)).toEqual(10);
});
it('expect curry4 to handle 2 + 1 + 1 args', () => {
expect(add4(1, 2)(3)(4)).toEqual(10);
});
it('expect curry4 to handle 1 + 1 + 1 + 1 args', () => {
expect(add4(1)(2)(3)(4)).toEqual(10);
});
const add3 = curry((a: number, b: number, c: number) => a + b + c);
it('expect curry3 to handle 3 + 0 args', () => {
expect(add3(1, 2, 3)).toEqual(6);
});
it('expect curry3 to handle 2 + 1 args', () => {
expect(add3(1, 2)(3)).toEqual(6);
});
it('expect curry3 to handle 1 + 2 args', () => {
expect(add3(1)(2, 3)).toEqual(6);
});
it('expect curry3 to handle 3 times 1 arg', () => {
expect(add3(1)(2)(3)).toEqual(6);
});
const add2 = curry((a: number, b: number) => a + b);
it('expect curry2 to handle 2 + 0 args', () => {
expect(add2(1, 2)).toEqual(3);
});
it('expect curry2 to handle 1 + 1 args', () => {
expect(add2(1)(2)).toEqual(3);
});
import {curry, _} from './curry'
export const _ = Symbol("gap");
interface CurriedFunction2<T1, T2, R> {
(t1: T1, t2?: T2): (t2: T2) => R;
(t1: T1, t2?: T2): R;
}
interface CurriedFunction3<T1, T2, T3, R> {
(t1: T1, t2?: T2, t3?: T3): CurriedFunction2<T2, T3, R>;
(t1: T1, t2?: T2, t3?: T3): (t3: T3) => R;
(t1: T1, t2?: T2, t3?: T3): R;
}
interface CurriedFunction4<T1, T2, T3, T4, R> {
(t1: T1, t2?: T2, t3?: T3, t4?: T4): CurriedFunction3<T2, T3, T4, R>;
(t1: T1, t2?: T2, t3?: T3, t4?: T4): CurriedFunction2<T3, T4, R>;
(t1: T1, t2?: T2, t3?: T3, t4?: T4): (t4: T4) => R;
(t1: T1, t2?: T2, t3?: T3, t4?: T4): R;
}
type CurriedFunction<T1, T2, T3, T4, R> = (t1: CurriedFunction<T1, T2, T3, T4, R>| T1, t2?: T2, t3?: T3, t4?: T4) => CurriedFunction<T1, T2, T3, T4, R> |
CurriedFunction2<T1, T2, R> |
CurriedFunction3<T1, T2, T3, R> |
CurriedFunction4<T1, T2, T3, T4, R> | R;
export function curry2<T1, T2, T3, T4, R>(f: (t1: T1, t2?: T2, t3?: T3, t4?: T4) => R): CurriedFunction2<T1, T2, R> {
function curriedFunction(t1: T1): (t2: T2) => R;
function curriedFunction(t1: T1, t2?: T2): R;
function curriedFunction(t1: T1, t2?: T2): any {
switch (arguments.length) {
case 1:
return function(t2: T2): R {
return f(t1, t2);
}
case 2:
if (t1 === _) {
return function(tt1: T1): R {
return f(tt1, t2);
}
}
return f(t1, t2);
default:
throw new SyntaxError(`${arguments.length} args found in ${arguments}`);
}
}
return curriedFunction;
}
export function curry3<T1, T2, T3, T4, R>(f: (t1: T1, t2?: T2, t3?: T3, t4?: T4) => R): CurriedFunction3<T1, T2, T3, R> {
function curriedFunction(t1: T1): CurriedFunction2<T2, T3, R>;
function curriedFunction(t1: T1, t2?: T2): (t3: T3) => R;
function curriedFunction(t1: T1, t2?: T2, t3?: T3): R;
function curriedFunction(t1: T1, t2?: T2, t3?: T3): any {
switch (arguments.length) {
case 1:
return curry2(function(t2: T2, t3?: T3): R {
return f(t1, t2, t3);
});
case 2:
if (t1 === _) {
return curry2(function(tt1: T1, tt3?: T3): R {
return f(tt1, t2, tt3);
});
}
return function(t3: T3): R {
return f(t1, t2, t3);
};
case 3:
if (t1 === _ && t2 === _) {
return curry2(function(tt1: T1, tt2?: T2): R {
return f(tt1, tt2, t3);
});
}
if (t1 === _) {
return function(tt1: T1): R {
return f(tt1, t2, t3);
};
}
if (t2 === _) {
return function(tt2: T2): R {
return f(t1, tt2, t3);
};
}
return f(t1, t2, t3);
default:
throw new SyntaxError(`${arguments.length} args found in ${arguments}`);
}
}
return curriedFunction;
}
export function curry4<T1, T2, T3, T4, R>(f: (t1: T1, t2?: T2, t3?: T3, t4?: T4) => R): CurriedFunction4<T1, T2, T3, T4, R> {
function curriedFunction(t1: T1): CurriedFunction3<T2, T3, T4, R>;
function curriedFunction(t1: T1, t2: T2): CurriedFunction2<T3, T4, R>;
function curriedFunction(t1: T1, t2: T2, t3: T3): (t4: T4) => R;
function curriedFunction(t1: T1, t2: T2, t3: T3, t4: T4): R;
function curriedFunction(t1: T1, t2?: T2, t3?: T3, t4?: T4): any {
switch (arguments.length) {
case 1:
return curry3(function(tt2: T2, tt3?: T3, tt4?: T4): R {
return f(t1, tt2, tt3, tt4);
});
case 2:
if (t1 === _) {
return curry3(function(tt1: T1, tt3?: T3, tt4?: T4): R {
return f(tt1, t2, tt3, tt4);
});
}
return curry2(function(tt3: T3, tt4?: T4): R {
return f(t1, t2, tt3, tt4);
});
case 3:
if (t1 === _ && t2 === _) {
return curry3(function(tt1: T1, tt2?: T2, tt4?: T4): R {
return f(tt1, tt2, t3, tt4);
});
}
if (t1 === _) {
return curry2(function(tt1: T1, tt4?: T4): R {
return f(tt1, t2, t3, tt4);
});
}
if (t2 === _) {
return curry2(function(tt2: T2, t4?: T4): R {
return f(t1, tt2, t3, t4);
});
}
return function(t4: T4): R {
return f(t1, t2, t3, t4);
};
case 4:
if (t1 === _ && t2 === _ && t3 === _) {
return curry3(function(tt1: T1, tt2?: T2, tt3?: T3): R {
return f(tt1, tt2, tt3, t4);
});
}
if (t1 === _ && t2 == _) {
return curry2(function(tt1: T1, tt2: T2): R {
return f(tt1, tt2, t3, t4);
});
}
if (t1 === _ && t3 === _) {
return curry2(function(tt1: T1, tt3?: T3): R {
return f(tt1, t2, tt3, t4);
});
}
if (t2 === _ && t3 === _) {
return curry2(function(tt2: T2, tt3?: T3): R {
return f(t1, tt2, tt3, t4);
});
}
if (t1 === _) {
return function(tt1: T1): R {
return f(tt1, t2, t3, t4);
};
}
if (t2 === _) {
return function(tt2: T2): R {
return f(t1, tt2, t3, t4);
};
}
if (t3 === _) {
return function(tt3: T3): R {
return f(t1, t2, tt3, t4);
};
}
return f(t1, t2, t3, t4);
default:
throw new SyntaxError(`${arguments.length} args found in ${arguments}`);
}
}
return curriedFunction;
}
export function curry<T1, T2, T3, T4, R>(f: CurriedFunction<T1, T2, T3, T4, R>): CurriedFunction4<T1, T2, T3, T4, R>;
export function curry<T1, T2, T3, T4, R>(f: CurriedFunction<T1, T2, T3, T4, R>): CurriedFunction3<T1, T2, T3, R>;
export function curry<T1, T2, T3, T4, R>(f: CurriedFunction<T1, T2, T3, T4, R>): CurriedFunction2<T1, T2, R>;
export function curry<T1, T2, T3, T4, R>(f: CurriedFunction<T1, T2, T3, T4, R>): (t1: T1, t2?: T2, t3?: T3, t4?:T4) => R;
export function curry<T1, T2, T3, T4, R>(f: CurriedFunction<T1, T2, T3, T4, R>): CurriedFunction<T1, T2, T3, T4, R> {
function countArgs(func : string): number {
const args = func.substring(func.indexOf('(') + 1, func.indexOf(')'));
if (args.trim().length === 0) {
return 0;
}
let pos = 0;
let cnt = 0;
while (pos >= 0) {
cnt++;
pos = args.indexOf(',', pos + 1);
}
return cnt;
}
switch (countArgs(f.toString())) {
case 0:
return f;
case 1:
return f;
case 2:
return curry2(f);
case 3:
return curry3(f);
case 4:
return curry4(f);
default:
throw new SyntaxError(`More than 4 args in ${f.toString()}`);
}
}
@waldogit
Copy link
Author

This git is inspired by a post from albrow (https://gist.github.com/donnut/fd56232da58d25ceecf1).
I extended his code

  • to support a variadic curry (1 to 4 arguments)
  • with the ability to curry a curried function, through the use of TS recursive types
  • and partially applying by including _ as gap arguments (Ramda.js style)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment