Last active
August 2, 2022 05:51
-
-
Save branneman/4ffb7ec3fc4a2091849ba5d56742960c to your computer and use it in GitHub Desktop.
Higher order auto curry function
This file contains 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
'use strict' | |
const curryN = (() => { | |
const isPlaceholder = x => x['@@functional/placeholder'] === true | |
const filterPlaceholders = xs => xs.filter(x => isPlaceholder(x)) | |
const filterValues = xs => xs.filter(x => !isPlaceholder(x)) | |
return (arity, fn, accIn = []) => (...args) => { | |
const accOut = accIn.slice() | |
if (filterPlaceholders(accIn).length > 0) { | |
filterValues(args).forEach(val => { | |
const i = accOut.findIndex(isPlaceholder) | |
const idx = i === -1 ? accOut.length : i | |
accOut[idx] = val | |
}) | |
} else { | |
accOut.push(...args) | |
} | |
if (filterValues(accOut).length >= arity) { | |
return fn.apply(null, accOut) | |
} | |
return curryN(arity, fn, accOut) | |
} | |
})() | |
const curry = fn => curryN(fn.length, fn) | |
const _ = { '@@functional/placeholder': true } | |
module.exports = { curry, curryN, _ } |
This file contains 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 { curryN, curry, _ } = require('./curry') | |
describe('curryN()', () => { | |
it('should correctly curry to given arity', () => { | |
const threeArg = (a, b, c = 42) => c | |
const fn = curryN(2, threeArg) | |
const res1 = fn() //=> fn with arity of 2 | |
const res2 = fn(1) //=> fn with arity of 1 | |
const res3 = fn(1, 2) //=> run function | |
const res4 = fn(1, 2, 3) //=> run function | |
const res5 = fn(1, 2, 3, 4) //=> run function | |
expect(typeof res1).toStrictEqual('function') | |
expect(typeof res2).toStrictEqual('function') | |
expect(res3).toStrictEqual(42) | |
expect(res4).toStrictEqual(3) | |
expect(res5).toStrictEqual(3) | |
}) | |
}) | |
describe('curry()', () => { | |
it('returns a new function on subsequent calls', () => { | |
const sum = (a, b) => a + b | |
const fn0 = curry(sum) | |
const fn1 = fn0() | |
const fn2 = fn1() | |
const fn3 = fn2() | |
expect(fn3(10, 5)).toStrictEqual(15) | |
}) | |
it('handles more arguments than given arity', () => { | |
// sumAll arity = 2 | |
const sumAll = curry(function(a, b) { | |
const xs = Array.from(arguments) | |
return xs.reduce((acc, curr) => acc + curr, 0) | |
}) | |
const result = [ | |
sumAll()(40)(20), | |
sumAll(20)(40), | |
sumAll(30, 30), | |
sumAll(10, 20, 30), | |
sumAll(10)(20, 30), | |
sumAll(10, 20, 30), | |
sumAll(10)(20, 20, 10), | |
sumAll(10)(10, 10, 10, 20) | |
] | |
expect(result.every(x => x === 60)).toBe(true) | |
}) | |
it('accepts an arity of 0', () => { | |
const give15 = () => 10 + 5 | |
const fn = curry(give15) | |
expect(fn()).toStrictEqual(15) | |
}) | |
it('accepts an arity of 1', () => { | |
const add5 = n => n + 5 | |
const fn = curry(add5) | |
expect(fn(10)).toStrictEqual(15) | |
}) | |
it('accepts an arity of 2', () => { | |
const sum = (a, b) => a + b | |
const fn = curry(sum) | |
const result = [fn()(10)(5), fn(10)(5), fn(10, 5)] | |
expect(result).toStrictEqual([15, 15, 15]) | |
}) | |
it('accepts an arity of 3', () => { | |
const sum3 = (a, b, c) => a + b + c | |
const fn = curry(sum3) | |
const result = [ | |
fn(10, 20, 30), | |
fn(10)(20, 30), | |
fn(10, 20)(30), | |
fn(10)(20)(30) | |
] | |
expect(result).toStrictEqual([60, 60, 60, 60]) | |
}) | |
it('accepts an arity of 12', () => { | |
const sum12 = (a, b, c, d, e, f, g, h, i, j, k, l) => | |
a + b + c + d + e + f + g + h + i + j + k + l | |
const fn = curry(sum12) | |
const result = [ | |
fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), | |
fn(1, 2)(3, 4, 5, 6, 7, 8, 9, 10, 11, 12), | |
fn(1, 2, 3, 4, 5, 6)(7)(8)(9, 10, 11, 12), | |
fn(1, 2)(3, 4)(5, 6, 7, 8)(9, 10, 11, 12), | |
fn(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12) | |
] | |
expect(result).toStrictEqual([78, 78, 78, 78, 78]) | |
}) | |
}) | |
describe('placeholder', () => { | |
it('it allows gaps via placeholder value', () => { | |
const sum3 = (a, b, c) => a + b + c | |
const fn = curry(sum3) | |
const result = [ | |
fn(10, 20, 30), | |
fn(_, 20, 30)(10), | |
fn(_, _, 30)(10)(20), | |
fn(_, _, 30)(10, 20), | |
fn(_, 20)(10)(30), | |
fn(_, 20)(10, 30), | |
fn(_, 20)(_, 30)(10) | |
] | |
expect(result.every(x => x === 60)).toBe(true) | |
}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment