Wondering what’s next for npm?Check out our public roadmap! »

    katsu-curry

    0.7.8 • Public • Published

    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) => ((+ 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) => ((+ 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

    Install

    npm i katsu-curry

    DownloadsWeekly Downloads

    392

    Version

    0.7.8

    License

    ISC

    Unpacked Size

    181 kB

    Total Files

    87

    Last publish

    Collaborators

    • avatar