diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d3501f..f884f56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Change Log ## [unreleased] +* Add `u._` placeholder for curried functions. ## [0.6.0] * Remove support for `_.placeholder` in curried methods. This may come back, but it was necessary for the next item. diff --git a/README.md b/README.md index a881d51..6a9d20b 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,19 @@ var result = u({ person: { [key]: 21 } }, { person: { name: 'Olivier P.', age: 2 expect(result).toEqual({ person: { name: 'Olivier P.', age: 21 } }); ``` +### `u._` + +All updeep functions are curried. +If you want to partially apply a function in an order other than the default argument order, you can use the placeholder. + +```js +function increment(i) { return i + 1; } +var updateJoe = u(u._, { name: "Joe Merrill", age: 21 }); +var result = updateJoe({ age: increment }); + +expect(result).toEqual({ name: "Joe Merrill", age: 22 }); +``` + ### `u.updateIn(path(, value)(, object))` Update a single value with a simple string or array path. diff --git a/lib/index.js b/lib/index.js index 72fb5b4..4c664d8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -8,9 +8,11 @@ import reject from './reject'; import update from './update'; import updateIn from './updateIn'; import withDefault from './withDefault'; +import { _ } from './util/curry'; const u = update; +u._ = _; u.if = _if; u.ifElse = ifElse; u.is = is; diff --git a/lib/util/curry.js b/lib/util/curry.js index 1727308..036a66d 100644 --- a/lib/util/curry.js +++ b/lib/util/curry.js @@ -1,7 +1,20 @@ -/* eslint no-shadow:0 */ +/* eslint no-shadow:0, no-param-reassign:0 */ +export const _ = '@@updeep/placeholder'; + +function countArguments(args, max) { + let n = args.length; + if (n > max) n = max; + + while (args[n - 1] === _) { + n--; + } + + return n; +} + export function curry1(fn) { return function curried(a, b, c) { - const n = arguments.length; + const n = countArguments(arguments); if (n >= 1) return fn(a, b, c); return curried; @@ -10,9 +23,17 @@ export function curry1(fn) { export function curry2(fn) { return function curried(a, b, c, d) { - const n = arguments.length; + const n = countArguments(arguments, 2); + + if (b === _ || c === _ || d === _) { + throw new Error('Can only use placeholder on first argument of this function.'); + } + + if (n >= 2) { + if (a === _) return curry1((a, c, d) => fn(a, b, c, d)); + return fn(a, b, c, d); + } - if (n >= 2) return fn(a, b, c, d); if (n === 1) return curry1((b, c, d) => fn(a, b, c, d)); return curried; }; @@ -20,22 +41,71 @@ export function curry2(fn) { export function curry3(fn) { return function curried(a, b, c, d, e) { - const n = arguments.length; + const n = countArguments(arguments, 3); + + if (c === _ || d === _ || e === _) { + throw new Error('Can only use placeholder on first or second argument of this function.'); + } + + if (n >= 3) { + if (a === _) { + if (b === _) return curry2((a, b, d, e) => fn(a, b, c, d, e)); + return curry1((a, d, e) => fn(a, b, c, d, e)); + } + if (b === _) return curry1((b, d, e) => fn(a, b, c, d, e)); + return fn(a, b, c, d, e); + } + + if (n === 2) { + if (a === _) return curry2((a, c, d, e) => fn(a, b, c, d, e)); + return curry1((c, d, e) => fn(a, b, c, d, e)); + } - if (n >= 3) return fn(a, b, c, d, e); - if (n === 2) return curry1((c, d, e) => fn(a, b, c, d, e)); if (n === 1) return curry2((b, c, d, e) => fn(a, b, c, d, e)); + return curried; }; } export function curry4(fn) { return function curried(a, b, c, d, e, f) { - const n = arguments.length; + const n = countArguments(arguments, 4); + + if (d === _ || e === _ || f === _) { + throw new Error('Can only use placeholder on first, second or third argument of this function.'); + } + + if (n >= 4) { + if (a === _) { + if (b === _) { + if (c === _) return curry3((a, b, c, e, f) => fn(a, b, c, d, e, f)); + return curry2((a, b, e, f) => fn(a, b, c, d, e, f)); + } + if (c === _) return curry2((a, c, e, f) => fn(a, b, c, d, e, f)); + return curry1((a, e, f) => fn(a, b, c, d, e, f)); + } + if (b === _) { + if (c === _) return curry2((b, c, e, f) => fn(a, b, c, d, e, f)); + return curry1((b, e, f) => fn(a, b, c, d, e, f)); + } + if (c === _) return curry1((c, e, f) => fn(a, b, c, d, e, f)); + return fn(a, b, c, d, e, f); + } + + if (n === 3) { + if (a === _) { + if (b === _) return curry3((a, b, d, e, f) => fn(a, b, c, d, e, f)); + return curry2((a, d, e, f) => fn(a, b, c, d, e, f)); + } + if (b === _) return curry2((b, d, e, f) => fn(a, b, c, d, e, f)); + return curry1((d, e, f) => fn(a, b, c, d, e, f)); + } + + if (n === 2) { + if (a === _) return curry3((a, c, d, e, f) => fn(a, b, c, d, e, f)); + return curry2((c, d, e, f) => fn(a, b, c, d, e, f)); + } - if (n >= 4) return fn(a, b, c, d, e, f); - if (n === 3) return curry1((d, e, f) => fn(a, b, c, d, e, f)); - if (n === 2) return curry2((c, d, e, f) => fn(a, b, c, d, e, f)); if (n === 1) return curry3((b, c, d, e, f) => fn(a, b, c, d, e, f)); return curried; }; diff --git a/perf/index.js b/perf/index.js index aa8ecbf..409ae88 100644 --- a/perf/index.js +++ b/perf/index.js @@ -3,12 +3,12 @@ const Benchmark = require('benchmark'); const u = require('../lib'); const _ = require('lodash'); -const { curry2 } = require('../lib/util/curry'); +const { curry4 } = require('../lib/util/curry'); -const add = (x, y) => x + y; +const add = (a, b, c, d) => a + b + c + d; const fakeCurryAdd = x => y => x + y; const curryAdd = _.curry(add); -const updeepCurry = curry2(add); +const updeepCurry = curry4(add); // const updeepCurryBig = curry.curryBig(add); const array = [0, 1, 2, 3, 4, 5]; @@ -44,8 +44,8 @@ function createSuite(suiteName, tests) { const curryVsLodash = createSuite('Curry', { - 'updeep curry partial call': () => updeepCurry(3)(4), - 'lodash curry partial call': () => curryAdd(3)(4), + 'updeep curry partial call': () => updeepCurry(3)(4)(5)(6), + 'lodash curry partial call': () => curryAdd(3)(4)(5)(6), }); const mapVsLodash = createSuite('Map', { @@ -66,6 +66,6 @@ const applyVsDestructure = createSuite('apply vs destructure', { 'destructure': () => fnDestructure(1, 2, 3, 4, 5), }); -// curryVsLodash(); -// mapVsLodash(); -applyVsDestructure(); +curryVsLodash(); +mapVsLodash(); +// applyVsDestructure(); diff --git a/test/updeep-spec.js b/test/updeep-spec.js index 0423a27..f05cee9 100644 --- a/test/updeep-spec.js +++ b/test/updeep-spec.js @@ -110,4 +110,12 @@ describe('updeep', () => { it('assigns null values', () => { expect(u({isNull: null}, {})).to.eql({isNull: null}); }); + + it('can use a placeholder to partially apply', () => { + function increment(i) { return i + 1; } + const updateJoe = u(u._, { name: 'Joe Merrill', age: 21 }); + const result = updateJoe({ age: increment }); + + expect(result).to.eql({ name: 'Joe Merrill', age: 22 }); + }); }); diff --git a/test/util/curry-spec.js b/test/util/curry-spec.js index f2ee36c..69b66c8 100644 --- a/test/util/curry-spec.js +++ b/test/util/curry-spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { curry1, curry2, curry3, curry4 } from '../../lib/util/curry'; +import { curry1, curry2, curry3, curry4, _ } from '../../lib/util/curry'; describe('curry1', () => { it('can curry one arguments', () => { @@ -26,12 +26,17 @@ describe('curry2', () => { const curried = curry2((a, b, c, d) => [a, b, c, d]); expect(curried(1, 2, 3, 4, 5)).to.eql([1, 2, 3, 4]); }); + + it('can use the placeholder', () => { + const curried = curry2((a, b, c, d) => [a, b, c, d]); + expect(curried(_, 2)(1, 3, 4)).to.eql([1, 2, 3, 4]); + }); }); describe('curry3', () => { it('can curry three arguments', () => { const add = curry3((x, y, z) => x + y + z); - expect(add(3)(4)(5)).to.equal(12); + expect(add(3, _)(4)(5)).to.equal(12); expect(add()(3)()(4, 5)).to.equal(12); expect(add(3, 4, 5)).to.equal(12); }); @@ -40,12 +45,20 @@ describe('curry3', () => { const curried = curry3((a, b, c, d, e) => [a, b, c, d, e]); expect(curried(1, 2, 3, 4, 5, 6)).to.eql([1, 2, 3, 4, 5]); }); + + it('can use the placeholder', () => { + const curried = curry3((a, b, c, d, e) => [a, b, c, d, e]); + expect(curried(_, 2)('a', 3, 4, 5)).to.eql(['a', 2, 3, 4, 5]); + expect(curried('b', _, 3)(2, 4, 5)).to.eql(['b', 2, 3, 4, 5]); + expect(curried(_, 2, 3)('c', 4, 5)).to.eql(['c', 2, 3, 4, 5]); + expect(curried(_, _, 3)('d', 2, 4, 5)).to.eql(['d', 2, 3, 4, 5]); + }); }); describe('curry4', () => { it('can curry four arguments', () => { const add = curry4((x, y, z, u) => x + y + z + u); - expect(add(3)(4)(5)(6)).to.equal(18); + expect(add(3, _)(4)(5)(6)).to.equal(18); expect(add()(3)()(4, 5, 6)).to.equal(18); expect(add(3, 4, 5, 6)).to.equal(18); }); @@ -54,4 +67,24 @@ describe('curry4', () => { const curried = curry4((a, b, c, d, e, f) => [a, b, c, d, e, f]); expect(curried(1, 2, 3, 4, 5, 6, 7)).to.eql([1, 2, 3, 4, 5, 6]); }); + + it('can use the placeholder', () => { + const curried = curry4((a, b, c, d, e, f) => [a, b, c, d, e, f]); + expect(curried(_, 2)('a', 3, 4, 5, 6)).to.eql(['a', 2, 3, 4, 5, 6]); + expect(curried(_, 2, 3)('b', 4, 5, 6)).to.eql(['b', 2, 3, 4, 5, 6]); + expect(curried(_, 2, 3, 4)('c', 5, 6)).to.eql(['c', 2, 3, 4, 5, 6]); + + expect(curried('d', _, 3)(2, 4, 5, 6)).to.eql(['d', 2, 3, 4, 5, 6]); + expect(curried('e', _, 3, 4)(2, 5, 6)).to.eql(['e', 2, 3, 4, 5, 6]); + + expect(curried('f', 2, _, 4)(3, 5, 6)).to.eql(['f', 2, 3, 4, 5, 6]); + + expect(curried(_, _, 3)('g', 2, 4, 5, 6)).to.eql(['g', 2, 3, 4, 5, 6]); + expect(curried(_, _, 3, 4)('h', 2, 5, 6)).to.eql(['h', 2, 3, 4, 5, 6]); + expect(curried(_, 2, _, 4)('i', 3, 5, 6)).to.eql(['i', 2, 3, 4, 5, 6]); + + expect(curried('j', _, _, 4)(2, 3, 5, 6)).to.eql(['j', 2, 3, 4, 5, 6]); + + expect(curried(_, _, _, 4)('k', 2, 3, 5, 6)).to.eql(['k', 2, 3, 4, 5, 6]); + }); });