katsu-curry

curry functions with placeholders or in object form

Usage no npm install needed!

<script type="module">
  import katsuCurry from 'https://cdn.skypack.dev/katsu-curry';
</script>

README

curry sviggies

🍛 for everyone

Coverage Status

This module gives you the ability to curry functions with custom placeholders, or with object values.

What is curry?

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

Debug mode

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.

Object-style curry

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)

Benchmark

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.)

Changelog

  • 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 and remapArray
  • 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 via curryObjectK and curryObjectN and pipe / compose to allow for easy composition.

API

I

The identity combinator

Parameters

  • x any anything

Examples

import {I} from 'katsu-curry'
const five = I(5)

Returns any x - whatever was given

K

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

curryObjectN

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

curryObjectN

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

curryObjectKN

Given object and expected keys, continually curry until expected keys are met

Parameters

  • expected Object expected object
    • expected.k Array expected keys
    • expected.n number minimum expected keys
  • fn function function to be curried

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

curryObjectKN

Given object and expected keys, continually curry until expected keys are met

Parameters

  • expected Object expected object
    • expected.k Array expected keys
    • expected.n number minimum expected keys
  • fn function function to be curried

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

composedToString

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

pipe

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

pipe

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

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

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

curryify

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

curryify

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

curry a given function so that it takes multiple arguments

Parameters

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

curry a given function so that it takes multiple arguments

Parameters

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

remapArray

easily remap an array by indices

Parameters

  • indices Array an array of indices to remap
  • arr Array an input array

Examples

import {remapArray} from 'katsu-curry/debug'
remapArray([2,1,0], [`up`, `is`, `what`]).join(` `) // "what is up"

Returns Array remapped array

remapArray

easily remap an array by indices

Parameters

  • indices Array an array of indices to remap
  • arr Array an input array

Examples

import {remapArray} from 'katsu-curry'
remapArray([2,1,0], [`up`, `is`, `what`]).join(` `) // "what is up"

Returns Array remapped array

remap

reframe any function with the arguments as you want, plus curry

Parameters

  • indices Array an array of indices to remap
  • fn Function a function

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)

remap

reframe any function with the arguments as you want, plus curry

Parameters

  • indices Array an array of indices to remap
  • fn Function a function

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)

curryObjectK

Given object and expected keys, continually curry until expected keys are met

Parameters

  • expected Array expected keys
  • fn function function to be curried

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

curryObjectK

Given object and expected keys, continually curry until expected keys are met

Parameters

  • expected Array expected keys
  • fn function function to be curried

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