Last active
April 18, 2016 20:29
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()}`); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This git is inspired by a post from albrow (https://gist.github.com/donnut/fd56232da58d25ceecf1).
I extended his code