π for everyone
This module gives you the ability to curry functions with custom placeholders, or with object values.
Currying is a way of modifying a given function which takes multiple arguments into a sequence of unary functions.
Specifically, in JS, it means that you can manipulate arguments, their order, and other facets of a passed in function.
Here's the barest bones version:
import {curry} from 'katsu-curry'
const add = curry((a, b, c) => a + b + c)
// all of these are equivalent
add(1)(2)(3) // 6
add(1, 2)(3) // 6
add(1, 2, 3) // 6
add(1)(2, 3) // 6
(A greater explanation of currying generally can be found here)
Here's an example of currying being slightly more useful:
// const {curry} = require('katsu-curry')
import {curry} from 'katsu-curry'
const lens = curry((property, fn, obj) => {
obj[property] = fn(obj[property])
return obj
})
const increment = (x) => ++x
const hey = {
brekk: {name: `brekk`, beers: 0},
you: {name: `you`, beers: 0},
}
[hey.brekk, hey.you].map(lens(`beers`, increment))
console.log(hey.brekk.beers) // 1
Part of the utility of this implementation is the debug mode, available from katsu-curry/debug
:
// const {curry} = require('katsu-curry/debug')
import {curry} from 'katsu-curry/debug' // identical API!
In debug mode, all currying functions (and in addition, all uses of pipe
/ compose
) are augmented to produce a .toString
function which is hopefully very helpful:
// const {curry, pipe} = require('katsu-curry/debug')
import {curry, pipe} from 'katsu-curry/debug'
const add = (a, b) => a + b
const divide = (a, b) => b / a
const multiply = (a, b) => b * a
const sum = curry(add)
const over = curry(divide)
const product = curry(multiply)
console.log(sum.toString()) // curry(add)(?,?)
console.log(sum(4).toString()) // curry(add)(4)(?)
const markupCost = pipe(
sum(2),
product(1.05)
)
console.log(markupCost.toString()) // pipe(curry(add)(2)(?), curry(multiply)(1.05)(?))
/*
we can see from the toString (which has to be single-line) what our pipe is made of:
pipe(
curry(add)(2)(?),
curry(multiply)(1.05)(?)
)
*/
This helpfulness comes at the cost of speed, however. The idea is that you can use the debug mode when trying to ascertain why something is broken or in places where speed is not a concern. See the benchmark below.
Inspired by this book and this library, you can also use object-style curried functions:
// const {curryObjectK} = require('katsu-curry')
import {curryObjectK} from 'katsu-curry'
const lens = curryObjectK(
[`prop`, `fn`, `obj`],
({prop, fn, obj}) => {
obj[prop] = fn(obj[prop])
return obj
}
)
const increment = (x) => ++x
const hey = {
brekk: {name: `brekk`, beers: 0},
you: {name: `you`, beers: 0},
}
[{obj: hey.brekk}, {obj: hey.you}].map(lens({prop: `beers`, fn: increment}))
console.log(hey.brekk.beers) // 1
See also the curryObjectKN
and curryObjectN
functions in the API below.
This library's implementation isn't as performant as it could be, but again, it has greater utility in debug mode:
// const {curryObjectK} = require('katsu-curry/debug')
import {curryObjectK} from 'katsu-curry/debug'
// in debug mode, named functions are extra helpful, as anonymous functions are, y'know, anonymous
const _add = (a, b) => a + b
const add = curryObjectK([`a`, `b`], _add)
console.log(add.toString()) // curry(_add)({a:?,b:?})
console.log(add({a: 2}).toString()) // curry(_add)({a:2})({b:?})
console.log(add({a: 2, b: 5})) // 7
// or if we misuse it in the future, the curry acts as a guard
console.log(add({a: 2, c: 100, d: 400, e: 1e3}).toString()) // curry(_add)({a:2})({b:?})
// and toString helps us identify the problem (no "b" param)
There are other implementations of standard curry which may be faster, though this implementation has a fast object-style function:
- katsu-curry #curryObjectN x 9,467,349 ops/sec Β±5.69% (73 runs sampled)
- @ibrokethat/curry x 9,168,538 ops/sec Β±6.90% (74 runs sampled)
- lodash/fp/curry x 8,873,327 ops/sec Β±4.29% (76 runs sampled)
- instant-curry x 7,059,247 ops/sec Β±4.83% (70 runs sampled)
- ramda/src/curry x 6,498,465 ops/sec Β±4.62% (71 runs sampled)
- katsu-curry #curry x 6,083,741 ops/sec Β±6.39% (67 runs sampled)
- just-curry-it x 4,601,812 ops/sec Β±4.89% (74 runs sampled)
- light-curry x 3,925,772 ops/sec Β±5.05% (71 runs sampled)
- katsu-curry/debug #curryObjectN x 3,679,708 ops/sec Β±6.08% (73 runs sampled)
- fjl-curry x 3,066,417 ops/sec Β±5.52% (70 runs sampled)
- dead-simple-curry x 2,823,668 ops/sec Β±5.66% (72 runs sampled)
- bloody-curry x 3,174,576 ops/sec Β±3.21% (77 runs sampled)
- curri x 2,634,989 ops/sec Β±4.64% (77 runs sampled)
- curry x 2,246,712 ops/sec Β±5.29% (70 runs sampled)
- fj-curry x 1,834,610 ops/sec Β±6.94% (69 runs sampled)
- curry-d x 1,573,489 ops/sec Β±6.73% (70 runs sampled)
- auto-curry x 1,343,690 ops/sec Β±3.79% (74 runs sampled)
- katsu-curry #curryObjectK x 1,159,314 ops/sec Β±5.70% (73 runs sampled)
- katsu-curry/debug #curry x 867,879 ops/sec Β±5.75% (70 runs sampled)
- @riim/curry x 444,138 ops/sec Β±7.84% (64 runs sampled)
- fpo.curryMultiple x 840,026 ops/sec Β±3.42% (76 runs sampled)
- fpo.curry x 945,169 ops/sec Β±4.40% (74 runs sampled)
- katsu-curry/debug #curryObjectK x 178,968 ops/sec Β±5.35% (71 runs sampled)
(See this file to view the tests, augment or run yourself.)
- 0.7.0 - Split out a
debug
version of the codebase which is slower but more useful - 0.6.0 - API changes, fixed publication
- 0.5.0 - API changes, added
remap
andremapArray
- 0.4.1 - streamlined build with
germs
- 0.4.0 - improvements in testing
- 0.3.1 - improvements for speed
- 0.1.1 - Fix solo exports
- 0.1.0 - Updated API and privatized some existing methods
- 0.0.8 - modularized the codebase
- 0.0.7 - .npmignore fixes
- 0.0.6 - adjustments to
toString
functionality (now deprecated) - 0.0.4 - Logo
- 0.0.3 - First working release, supports regular currying via
curry
and currying by object viacurryObjectK
andcurryObjectN
andpipe
/compose
to allow for easy composition.
The identity combinator
Parameters
x
any anything
Examples
import {I} from 'katsu-curry'
const five = I(5)
Returns any x - whatever was given
The constant combinator
Parameters
x
any anything
Examples
import {K} from 'katsu-curry'
const fiveFn = K(5)
const twoFn = K(2)
fiveFn() * twoFn() // 10
Returns function a function which eventually returns x
Given object with n keys, continually curry until n keys are met
Parameters
Examples
import {curryObjectN} from 'katsu-curry'
const threeKeyProps = curryObjectN(3, Object.keys)
threeKeyProps({a: 1, b: 2, c: 3}) // [`a`, `b`, `c`]
threeKeyProps({a: 1, b: 2}) // function expecting one more param
Returns function invoked function or partially applied function
Given object with n keys, continually curry until n keys are met
Parameters
Examples
import {curryObjectN} from 'katsu-curry/debug'
const threeKeyProps = curryObjectN(3, Object.keys)
threeKeyProps({a: 1, b: 2, c: 3}) // [`a`, `b`, `c`]
threeKeyProps({a: 1, b: 2}) // function expecting one more param
threeKeyProps({a: 1, b: 2}).toString() // curry(keys)({0,1})({2:?})
Returns function invoked function or partially applied function
Given object and expected keys, continually curry until expected keys are met
Parameters
Examples
// import {curryObjectKN} from 'katsu-curry/debug'
import {curryObjectKN} from 'katsu-curry'
const setTheTable = curryObjectKN({
k: [`knives`, `forks`, `spoons`],
n: 4
}, ({knives, forks, spoons, drinks = [`wine`]}) => (
`${knives} x ${forks} + ${spoons} + ${drinks}`
))
const setTheKnivesAndSpoons = setTheTable({forks: [0,1,2,3]}) // partial-application!
Returns function invoked function or partially applied function
Given object and expected keys, continually curry until expected keys are met
Parameters
Examples
// import {curryObjectKN} from 'katsu-curry/debug'
import {curryObjectKN} from 'katsu-curry/debug'
const setTheTable = curryObjectKN({
k: [`knives`, `forks`, `spoons`],
n: 4
}, function placeSet({knives, forks, spoons, drinks = [`wine`]}) (
`${knives} x ${forks} + ${spoons} + ${drinks}`
))
const setTheKnivesAndSpoons = setTheTable({forks: [0,1,2,3]}) // partial-application!
setTheKnivesAndSpoons.toString() // curry(placeSet)({forks})({knives:?,spoons:?})
Returns function invoked function or partially applied function
generate a string which represents the ongoing partial-application view
Parameters
args
Array<string> a list of arguments (optional, default[]
)name
string = 'pipe' - the name for your composed function (optional, default`pipe`
)
Returns function a function which could be used as a toString
function
Use the placeholder to specify "gaps" in the partial application of a function.
Examples
import {curry, $} from 'katsu-curry'
const divide = curry((x, y) => x / y)
const twoOver = divide(2) // limited utility!
twoOver(100) // 0.02
const half = divide($, 2) // placehold the x parameter!
half(100) // 50
compose functions, from left to right (or top to bottom, depending on your perspective)
Examples
import {pipe} from 'katsu-curry/debug'
const multiply = curry(function mult(x, y) { return x * y }) // named inner function
const divide = curry(function div(x, y) { return x / y})
const twice = multiply(2)
const half = divide($, 2)
const x = Math.round(Math.random() * 10)
pipe(half, twice)(x) === twice(half(x)) // true
const identity = pipe(half, twice) // (x / 2) * 2 === x
identity.toString() // pipe(curry(div)(π,2), curry(mult)(2)(?))
Returns function a composed function
compose functions, left to right
Examples
import {pipe} from 'katsu-curry'
const f = (x) => x * 2
const g = (x) => x / 2
const a = Math.round(Math.random() * 10)
pipe(f, g)(a) === g(f(a)) // true
Returns function a composed function
compose functions, right to left
Examples
import {compose, curry, $} from 'katsu-curry/debug'
const multiply = curry(function mult(x, y) { return x * y }) // named inner function
const divide = curry(function div(x, y) { return x / y})
const twice = multiply(2)
const half = divide($, 2)
const x = Math.round(Math.random() * 10)
compose(half, twice)(x) === half(twice(x)) // true
const identity = compose(half, twice)
identity.toString() // compose(curry(mult)(2)(?), curry(div)(π,2))
Returns function a composed function
compose functions, right to left
Examples
import {compose} from 'katsu-curry'
const f = (x) => x * 2
const g = (x) => x / 2
const a = Math.round(Math.random() * 10)
compose(f, g)(a) === f(g(a)) // true
Returns function a composed function
Pass currify a test which validates placeholders, and it will give you back a function which curries other functions
Parameters
test
function a function which asserts whether a given parameter is a placeholder
Examples
import { curryify } from 'katsu-curry/debug'
const test = (x) => x === 3
// help me
const curry = curryify(test)
const addThenDivide = (a, b, c) => a + b / c
const theMagicNumber = addThenDivide(3, 2, 1)
const two = theMagicNumber(0) // apparently, it's 2
Returns function a function which curries other functions
Pass currify a test which validates placeholders, and it will give you back a function which curries other functions
Parameters
test
function a function which asserts whether a given parameter is a placeholder
Examples
import { curryify } from 'katsu-curry'
const test = (x) => x === 3
// help me
const curry = curryify(test)
const addThenDivide = (a, b, c) => a + b / c
const theMagicNumber = addThenDivide(3, 2, 1)
const two = theMagicNumber(0) // apparently, it's 2
Returns function a function which curries other functions
curry a given function so that it takes multiple arguments
Parameters
fn
function any function
Examples
import {curry, $} from 'katsu-curry/debug'
const divide = curry((a, b) => a / b)
const half = divide($, 2)
const twoOver = divide(2)
Returns function a curried function
curry a given function so that it takes multiple arguments
Parameters
fn
function any function
Examples
import {curry, $} from 'katsu-curry'
const divide = curry((a, b) => a / b)
const half = divide($, 2)
const twoOver = divide(2)
Returns function a curried function
easily remap an array by indices
Parameters
Examples
import {remapArray} from 'katsu-curry/debug'
remapArray([2,1,0], [`up`, `is`, `what`]).join(` `) // "what is up"
Returns Array remapped array
easily remap an array by indices
Parameters
Examples
import {remapArray} from 'katsu-curry'
remapArray([2,1,0], [`up`, `is`, `what`]).join(` `) // "what is up"
Returns Array remapped array
reframe any function with the arguments as you want, plus curry
Parameters
Examples
import {remap} from 'katsu-curry/debug'
const quaternaryFunction = (a, b, c, d) => ((a + b + c) / d)
const quaternaryFunctionLastShuffle = remap([1, 2, 3, 0], quaternaryFunction)
quaternaryFunctionLastShuffle(1, 2, 3, 4) === ((2 + 3 + 4) / 1)
reframe any function with the arguments as you want, plus curry
Parameters
Examples
import {remap} from 'katsu-curry'
const quaternaryFunction = (a, b, c, d) => ((a + b + c) / d)
const quaternaryFunctionLastShuffle = remap([1, 2, 3, 0], quaternaryFunction)
quaternaryFunctionLastShuffle(1, 2, 3, 4) === ((2 + 3 + 4) / 1)
Given object and expected keys, continually curry until expected keys are met
Parameters
Examples
import {curryObjectK} from 'katsu-curry/debug'
const abcProps = curryObjectK([`a`, `b`, `c`], function abc({a, b, c, optional = 1}) {
return a + b + c / optional
})
abcProps({a: 1, b: 2, c: 3}) // 6
abcProps({a: 1, b: 2}) // function expecting one more param
abcProps({a: 1, b: 2}).toString() // curry(abc)({a,b})({c:?})
abcProps({a: 1, b: 2, c: 3, optional: 10}) // 0.6
Returns function invoked function or partially applied function
Given object and expected keys, continually curry until expected keys are met
Parameters
Examples
import {curryObjectK} from 'katsu-curry'
const abcProps = curryObjectK([`a`, `b`, `c`], ({a, b, c, optional = 1}) => {
return a + b + c / optional
})
abcProps({a: 1, b: 2, c: 3}) // 6
abcProps({a: 1, b: 2}) // function expecting one more param
abcProps({a: 1, b: 2, c: 3, optional: 10}) // 0.6
Returns function invoked function or partially applied function