Open dotnetCarpenter opened 3 years ago
Actually after looking at the function for while I decided an easier to read curry
function might be one that is recursive only with itself.
function curry (f) {
return isEmpty(f)
? f()
: (a, ...rest) => {
let curriedF = f.bind(null, a)
return isEmpty(rest)
? curry(curriedF)
: curry(curriedF)(...rest)
}
}
function isEmpty (l) {
return l.length === 0
}
It also dawned on me that curryAccumulator
might be a confusing name since the accumulation was happening in the anonymous function parameters which does the job of separating the first argument with the rests of the arguments, (a, ...rest) =>
.
The same tests are of course still valid:
const add = (x, y, z) => x + y + z
console.log(
curry(add)(1,2,3), // 6
curry(add)(1,2)(3), // 6
curry(add)(1)(2)(3), // 6
curry(add).length === 1, // true
curry(add)(1).length === 1, // true
curry(add)(1, 1).length === 1, // true
curry(add)(1)(1).length === 1, // true
// curry(add)(1,2,3,4), // TypeError
// curry(add)(1,2)(3,4), // TypeError
// curry(add)(1)(2)(3)(4), // TypeError
)
a quick post to line up the 3 curry
functions against each other
Silently ignores extra arguments and does not always return a function take a single argument (e.i. cf.length !== 1).
// curry :: ((a, b, ...) -> c) -> a -> b -> ... -> c
function curry(fn) {
const arity = fn.length;
return function $curry(...args) {
if (args.length < arity) {
return $curry.bind(null, ...args);
}
return fn(...args); // used to be: fn.call(null, ...args);
};
}
// curry :: ((a, b, ...) -> c) -> a -> b -> ... -> c
function curry (f) {
if (f.length === 0) return f()
return (a, ...rest) => continuation(f.bind(null, a), rest)
}
function continuation(f, rest) {
return rest.length === 0
? curry(f)
: curry(f)(...rest)
}
// curry :: ((a, b, ...) -> c) -> a -> b -> ... -> c
function curry (f) {
return isEmpty(f)
? f()
: (a, ...rest) => {
let curriedF = f.bind(null, a)
return isEmpty(rest)
? curry(curriedF)
: curry(curriedF)(...rest)
}
}
function isEmpty (l) { return l.length === 0 }
While I appreciate the simplicity of the
curry
implementation it does go against the general definition of Currying. I suggest an implementation that via a co-recursive accumulator function will apply each argument to a new function as in the wikipedia definition1.My main issue with the current implementation is that it silently ignore extra arguments which makes it rather difficult to trace extraneous arguments when function composition goes wrong. This also seem more inline with how Lisp is implemented (does not allow too many arguments).
Consider the following, with the current implementation:
Wrong usage of the
add
function, silently ignores data, leading to subtle and hard to find bugs.Now consider the following implementation and results:
_It is not permitted to supply too many arguments to a curried function. Doing so will throw a
TypeError
._One limitation of my suggestion is that zero parameter functions will be invoked immediately which is never desirable.
Will fail with TypeError: curry(...) is not a function, with my implementation.
But currying zero parameter function also seem like a very edge case, adding an unnecessary layer of execution, that is better solved by having a
partial
for one-off function wrapping.partial
implementation.Lastly, my implementation does not let the user specify an
arity
which might be needed for variadic functions but neither does the one in Appendix A.My implementation is meant as a mean to start a discussion about popularise a curry function that actually turns a function into a sequence of functions that each take a single argument.
I apologise if this has been debated to death. It's not my intention to nag.
1: I have yet to find a formal definition of currying other than the wikipedia article.