Skip to content

Commit

Permalink
Merge pull request #401 from smartprocure/recurry
Browse files Browse the repository at this point in the history
Recurry
  • Loading branch information
daedalus28 committed Feb 7, 2023
2 parents 093ea89 + 260d734 commit e2ae0b9
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 17 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 1.72.0

- Add `recurry`
- Add `uncurry`
- Add `unlessTruthy`
- Add argument spreading support to logic functions
- Add additional arrayLens test case

# 1.71.8

- CI/CD correction
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "futil-js",
"version": "1.71.8",
"version": "1.72.0",
"description": "F(unctional) util(ities). Resistance is futile.",
"main": "lib/futil-js.js",
"scripts": {
Expand Down
17 changes: 17 additions & 0 deletions src/function.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,23 @@ let currier =
*/
export let flurry = currier(_.flow)

/**
* Uncurry allows curried functions to be called with all its arguments at once. Methods curried with lodash or ramda support this call style out of the box, but hand curried methods. This can happen as the result of function composition.
*
* @signature (arg -> arg -> arg) -> (arg, arg, arg)
*/
export let uncurry =
(fn) =>
(...args) =>
args.reduce((fn, arg) => fn(arg), fn)

/**
* Resets curry arity. Useful in scenarios where you have a curried function whose arity isn't detectable by a lodash or ramda curry - such as one constructed via function composition.
*
* @signature (n, fn) -> fn(arg1, ...argN)
*/
export let recurry = (n, fn) => _.curryN(n, uncurry(fn))

/**
* Returns a function that applies the mapping operation to all of the arguments of a function. Very similar to _.overArgs, but runs a single mapper on all of the args args.
*
Expand Down
27 changes: 16 additions & 11 deletions src/logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,30 @@ let boolIteratee = (x) =>
/**
* http://ramdajs.com/docs/#ifElse. The transform function T supports passing a boolean for `condition` as well as any valid argument of `_.iteratee`, e.g. `myBool = applyTest(x); F.ifElse(myBool, doSomething, doSomethingElse);`
*
* @signature (condition, onTrue, onFalse, x) -> (T(condition)(x) ? onTrue(x) : onFalse(x))
* @signature (condition, onTrue, onFalse, ...x) -> (T(condition)(...x) ? onTrue(...x) : onFalse(...x))
*/
export let ifElse = _.curry((condition, onTrue, onFalse, x) =>
boolIteratee(condition)(x)
? callOrReturn(onTrue, x)
: callOrReturn(onFalse, x)
export let ifElse = _.curryN(4, (condition, onTrue, onFalse, ...x) =>
boolIteratee(condition)(...x)
? callOrReturn(onTrue, ...x)
: callOrReturn(onFalse, ...x)
)

/**
* http://ramdajs.com/docs/#when. `T` extends `_.iteratee` as above.
*
* @signature (condition, onTrue, x) -> (T(condition)(x) ? onTrue(x) : _.identity(x))
* @signature (condition, onTrue, ...x) -> (T(condition)(...x) ? onTrue(...x) : _.identity(...x))
*/
export let when = _.curry((condition, t, x) =>
ifElse(condition, t, _.identity, x)
export let when = _.curryN(3, (condition, t, ...x) =>
ifElse(condition, t, _.identity, ...x)
)

/**
* http://ramdajs.com/docs/#unless. `T` extends `_.iteratee` as above.
*
* @signature (condition, onFalse, x) -> (T(condition)(x) ? _.identity(x) : onFalse(x))
* @signature (condition, onFalse, ...x) -> (T(condition)(...x) ? _.identity(...x) : onFalse(...x))
*/
export let unless = _.curry((condition, f, x) =>
ifElse(condition, _.identity, f, x)
export let unless = _.curryN(3, (condition, f, ...x) =>
ifElse(condition, _.identity, f, ...x)
)

/**
Expand All @@ -50,3 +50,8 @@ export let whenTruthy = when(Boolean)
* `when` curried with `exists`
*/
export let whenExists = when(exists)

/**
* `unless` curried with `Boolean`
*/
export let unlessTruthy = unless(Boolean)
19 changes: 19 additions & 0 deletions test/function.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,25 @@ describe("Function Functions", () => {
// Passing 1 at a time
expect(F.flurry(add, double)(1)(4)).to.equal(10)
})
it("uncurry", () => {
let curriedAdd = (x) => (y) => (z) => x + y + z
let uncurriedAdd = F.uncurry(curriedAdd)
expect(uncurriedAdd(1, 2, 3)).to.equal(6)
})
it("recurry", () => {
let addToElements = _.flow(_.add, _.map)
// Less terse implementations for refernce:
// let addToElements = number => _.map(_.add(number))
// let addToElements = (number, collection) = _.map(_.add(number), collection)

// Does not work correctly since flow doesn't detect
expect(addToElements(5, [1, 2, 3])).not.to.deep.equal([6, 7, 8])

let fn = F.recurry(2, addToElements)
// Recurried to support both ways of calling
expect(fn(5, [1, 2, 3])).to.deep.equal([6, 7, 8])
expect(fn(5)([1, 2, 3])).to.deep.equal([6, 7, 8])
})
it("mapArgs", () => {
let add = (x, y) => x + y
let double = (x) => x * 2
Expand Down
20 changes: 17 additions & 3 deletions test/lens.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,25 @@ describe("Lens Functions", () => {
}
let lens = arrayLens(false)
F.on(lens)()
expect(lens[0]).to.be.true
expect(F.view(lens)).to.be.true
F.off(lens)()
expect(lens[0]).to.be.false
expect(F.view(lens)).to.be.false
F.flip(lens)()
expect(lens[0]).to.be.true
expect(F.view(lens)).to.be.true

let object = { a: 1 }
let lens2 = [
object.a,
(x) => {
object.a = x
},
]
expect(F.view(lens2)).to.equal(1)
F.sets(2, lens2)()
expect(object.a).to.equal(2)

// This doesn't work because the value is snapshotted at array isntantiation
// expect(F.view(lens2)).to.equal(2)
})
it("functionPairLens", () => {
let object = {
Expand Down
15 changes: 15 additions & 0 deletions test/logic.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ describe("Logic Functions", () => {
)
).to.equal("6 is even!")
})
it("should spread multiple args", () => {
let divideBigBySmall = F.ifElse(
(x, y) => x > y,
(x, y) => x / y,
(x, y) => y / x
)
expect(divideBigBySmall(6, 2)).to.equal(3)
expect(divideBigBySmall(2, 6)).to.equal(3)
})
})
it("when", () => {
let clamp5 = F.when(
Expand Down Expand Up @@ -88,4 +97,10 @@ describe("Logic Functions", () => {
expect(fn(null)).to.equal(null)
expect(fn(false)).to.be.false
})
it("unlessTruthy", () => {
let fn = F.unlessTruthy(5)
expect(fn(3)).to.equal(3)
expect(fn(null)).to.equal(5)
expect(fn(false)).to.equal(5)
})
})

0 comments on commit e2ae0b9

Please sign in to comment.