Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add curryN to the helper functions #20

Merged
merged 1 commit into from
Jan 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,12 @@ When you want to branch a computation into two parts, this is the function you w
#### `compose : ((y -> z), (x -> y), ..., (a -> b)) -> a -> z`
While the `composeB` can be used to create a composition of two functions, there are times when you want to compose an entire flow together. That is where `compose` is useful. With `compose` you can create a right-to-left composition of functions. It will return you a function that represents your flow. Not really sold on writing flows from right-to-left? Well then, I would recommend reaching for `pipe`.

#### `curry : ((a, b, c) -> d) -> a -> b -> c -> d`
#### `curry : ((a, b, ...) -> z) -> a -> b -> ... -> z`
Pass this function a function and it will return you a function that can be called in any form that you require until all arguments have been provided. For example if you pass a function: `f : (a, b, c) -> d` you get back a function that can be called in any combination, such as: `f(x, y, z)`, `f(x)(y)(z)`, `f(x, y)(z)`, or even `f(x)(y, z)`. This is great for doing partial application on functions for maximum reusability.

#### `curryN : Number -> ((a, b, ...) -> z) -> a -> b -> ... -> z`
When dealing with variable argument functions, there are times when you may want to curry a specific number of arguments. Just pass this function the number of arguments you want to curry as well as the function, and it will not call the wrapped function until all arguments have been passed. This function will ONLY pass the number of arguments that you specified, any remaining arguments are discarded. This is great for limiting the arity of a given function when additional parameters are defaulted or not needed. This function will not auto curry functions returning functions like `curry` does, use with `curry` if you need to do some fancy setup for your wrapped function's api.

#### `ifElse : ((a -> Boolean) | Pred) -> (a -> b) -> (a -> c) -> a -> b | c`
Whenever you need to modify a value based some condition and want a functional way to do it without some imperative `if` statement, then reach for `ifElse`. This function take a predicate (some function that returns a Boolean) and two functions. The first is what is executed when the predicate is true, the second on a false condition. This will return a function ready to take a value to run through the predicate. After the value is evaluated, it will be ran through it's corresponding function, returning the result as the final result. This function comes in really handy when creating lifting functions for Sum Types (like `Either` or `Maybe`).

Expand Down
1 change: 1 addition & 0 deletions crocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const helpers = {
branch: require('./funcs/branch'),
compose: require('./funcs/compose'),
curry: require('./funcs/curry'),
curryN: require('./funcs/curryN'),
ifElse: require('./funcs/ifElse'),
inspect: require('./funcs/inspect'),
liftA2: require('./funcs/liftA2'),
Expand Down
2 changes: 2 additions & 0 deletions crocks.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const substitution = require('./combinators/substitution')
const branch = require('./funcs/branch')
const compose = require('./funcs/compose')
const curry = require('./funcs/curry')
const curryN = require('./funcs/curryN')
const ifElse = require('./funcs/ifElse')
const inspect = require('./funcs/inspect')
const liftA2 = require('./funcs/liftA2')
Expand Down Expand Up @@ -94,6 +95,7 @@ test('entry', t => {

t.equal(crocks.compose, compose, 'provides the compose function')
t.equal(crocks.curry, curry, 'provides the curry function')
t.equal(crocks.curryN, curryN, 'provides the curryN function')
t.equal(crocks.ifElse, ifElse, 'provides the ifElse function')
t.equal(crocks.inspect, inspect, 'provides the inspect function')
t.equal(crocks.liftA2, liftA2, 'provides the liftA2 function')
Expand Down
8 changes: 5 additions & 3 deletions funcs/curry.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ test('curry', t => {

t.ok(isFunction(curry), 'curry is a function')

t.throws(curry, TypeError, 'throws TypeError when nothing passed')

t.throws(c(undefined), TypeError, 'throws TypeError when undefined passed')
t.throws(c(null), TypeError, 'throws TypeError when null passed')

t.throws(curry, TypeError, 'throws TypeError when nothing passed')
t.throws(c(''), TypeError, 'throws TypeError when falsey string passed')
t.throws(c('string'), TypeError, 'throws TypeError when truthy string passed')
t.throws(c(0), TypeError, 'throws TypeError when falsy number passed')
t.throws(c(1), TypeError, 'throws TypeError when truthy number passed')
t.throws(c(false), TypeError, 'throws TypeError when false passed')
t.throws(c(true), TypeError, 'throws TypeError when true passed')
t.throws(c({}), TypeError, 'throws TypeError when object passed')
t.throws(c([]), TypeError, 'throws TypeError when array passed')

t.ok(isFunction(curry(noop)), 'returns a function')

Expand Down Expand Up @@ -51,7 +53,7 @@ test('curried function with multiple parameters', t => {
t.equal(func(1, 2, 3), sumThree(1, 2, 3), 'returns the result when fully applied')
t.equal(func(1)(2)(3), sumThree(1, 2, 3), 'returns the result when curried')
t.equal(func(1, 2)(3), sumThree(1, 2, 3), 'returns the result when called (_, _)(_)')
t.equal(func(1)(2, 3), sumThree(1, 2, 3), 'returns the result when called (_), (_, _)')
t.equal(func(1)(2, 3), sumThree(1, 2, 3), 'returns the result when called (_)(_, _)')
t.equal(func(1, 2, 3, 4, 5), sumThree(1, 2, 3), 'ignores extra parameters')

t.end()
Expand Down
32 changes: 32 additions & 0 deletions funcs/curryN.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/** @license ISC License (c) copyright 2016 original and current authors */
/** @author Ian Hofmann-Hicks (evil) */

const isNumber = require('../internal/isNumber')
const isFunction = require('../internal/isFunction')
const argsArray = require('../internal/argsArray')

function curryN(n, fn) {
if(!isNumber(n)) {
throw new TypeError('curryN: Number required for first argument')
}
if(!isFunction(fn)) {
throw new TypeError('curryN: Function required for second argument')
}

return function() {
const xs =
argsArray(arguments)

const args =
xs.length ? xs : [ undefined ]

const remaining =
Math.floor(n) - args.length

return (remaining > 0)
? curryN(remaining, Function.bind.apply(fn, [ null ].concat(args)))
: fn.apply(null, args.slice(0, n))
}
}

module.exports = curryN
78 changes: 78 additions & 0 deletions funcs/curryN.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const test = require('tape')
const sinon = require('sinon')
const helpers = require('../test/helpers')

const curryN = require('./curryN')

const noop = helpers.noop
const bindFunc = helpers.bindFunc
const isFunction = require('../internal/isFunction')

test('curryN errors', t => {
const c = bindFunc(curryN)

t.ok(isFunction(curryN), 'curryN is a function')

t.throws(curryN, TypeError, 'throws when nothing passed')

t.throws(c(undefined, noop), TypeError, 'throws when undefined passed in first argument')
t.throws(c(null, noop), TypeError, 'throws when null passed in first argument')
t.throws(c('', noop), TypeError, 'throws when falsey string passed in first argument')
t.throws(c('string', noop), TypeError, 'throws when truthy string passed in first argument')
t.throws(c(false, noop), TypeError, 'throws when false passed in first argument')
t.throws(c(true, noop), TypeError, 'throws when true passed in first argument')
t.throws(c(noop, noop), TypeError, 'throws when function passed in first argument')
t.throws(c({}, noop), TypeError, 'throws when object passed in first argument')
t.throws(c([], noop), TypeError, 'throws when array passed in first argument')

t.throws(c(1, undefined), TypeError, 'throws when undefined passed in second argument')
t.throws(c(1, null), TypeError, 'throws when null passed in second argument')
t.throws(c(1, ''), TypeError, 'throws when falsey string passed in second argument')
t.throws(c(1, 'string'), TypeError, 'throws when truthy string passed in second argument')
t.throws(c(1, 0), TypeError, 'throws when falsey number passed in second argument')
t.throws(c(1, 32), TypeError, 'throws when truthy number passed in second argument')
t.throws(c(1, false), TypeError, 'throws when false passed in second argument')
t.throws(c(1, true), TypeError, 'throws when true passed in second argument')
t.throws(c(1, {}), TypeError, 'throws when object passed in second argument')
t.throws(c(1, []), TypeError, 'throws when array passed in second argument')

t.ok(isFunction(curryN(1, noop)), 'returns a function')

t.end()
})

test('curryN function functionality', t => {
const result = 'result'
const f = sinon.spy(() => result)
const curried = curryN(3, f)

t.equal(curried(2, 3, 1), result, 'returns the result when fully applied')
t.equal(curried(2)(3)(1), result, 'returns the result when curried')
t.equal(curried(1, 2)(3), result, 'returns the result when called (_, _)(_)')
t.equal(curried(1)(2, 3), result, 'returns the result when called (_)(_, _)')

curried(1, 2, 3, 4, 5)
t.equal(f.lastCall.args.length, 3, 'only applies N arguments')

t.equal(curried()()(), result, 'returns the result when curried with no args')
t.same(f.lastCall.args, [ undefined, undefined, undefined ], 'applies undefineds when curried with no args')

t.end()
})

test('curry0 called with arguments', t => {
const f = sinon.spy(() => 'string')
const curried = curryN(0, f)

t.equal(curried(1, 2, 3), 'string', 'returns the result')
t.equal(f.lastCall.args.length, 0, 'does not pass arguments to function')
t.end()
})

test('curry0 called with no arguments', t => {
const f = sinon.spy(() => 'string')
const curried = curryN(0, f)

t.equal(curried(), 'string', 'returns the result')
t.end()
})