Currying
This is an in-depth explanation of currying. For simple usage examples, check out the "Getting started" page.
What is it?
Currying is the technique of transforming a regular function into one that can be called with a prefix of its parameters, in which case the function doesn't return its usual result. Instead it returns another curried function that accepts the remaining parameters.
For example, consider the following function:
const sum = (a, b, c) => a + b + c
Without currying, the only way to call it would be with three arguments, like
sum(1, 2, 3)
, but with currying it could be called in any of the following
ways:
sum(1, 2, 3)
sum(1, 2)(3)
sum(1)(2, 3)
sum(1)(2)(3)
How is it used in lfi
?
lfi
's functions are automatically curried where applicable1 and their
parameters are perfectly arranged for use in the pipe
function, which provides
a syntax similar to
method chaining without giving
up
tree shaking2.
For example, consider the following code:
import {
filter,
flatMap,
map,
pipe,
reduce,
toGrouped,
toMap,
toSet,
} from 'lfi'
import zoo from 'zoo'
const getSlothNamesByAgeWithCurrying = () =>
pipe(
zoo.exhibits,
flatMap(exhibit => exhibit.animals),
filter(animal => animal.species === `sloth`),
map(sloth => [sloth.age, sloth.name]),
reduce(toGrouped(toSet(), toMap())),
)
console.log(getSlothNamesByAgeWithCurrying())
const getSlothNamesByAgeWithoutCurrying = () =>
reduce(
toGrouped(toSet(), toMap()),
map(
sloth => [sloth.age, sloth.name],
filter(
animal => animal.species === `sloth`,
flatMap(exhibit => exhibit.animals, zoo.exhibits),
),
),
)
console.log(getSlothNamesByAgeWithoutCurrying())
getSlothNamesByAgeWithCurrying
is easier to read because the operation order
matches the reading order. It works because:
-
Each function call returns a function that accepts an iterable.
For example,
filter
accepts two parameters: a predicate function and an iterable, but infilter(animal => animal.species === `sloth`)
it's called with one argument. Thus, the expression evaluates to a function that accepts only the second parameter, an iterable. -
pipe
passes its first argument, an iterable, through each function argument in order, and then returns the last operation's result.
The result? Readable code that tree shakes perfectly. The functions imported in the code above bundle to just 790 B gzipped!
Footnotes
-
lfi
has many zero or one parameter functions, which are pointless to curry, and a few variadic functions, which are impossible to curry. ↩ -
Tree shaking class and object literal methods is really hard. ↩