I wrote a simple curry
function in JavaScript which works correctly for most cases:
var add = curry(function (a, b, c) {
return a + b + c;
});
var add2 = add(2);
var add5 = add2(3);
alert(add5(5));
<script>
function curry(f) {
var length = f.length;
if (length > 0) {
return partial(f, length, []);
} else {
return f; // f is already curried
}
}
function partial(f, length, a) {
return function () {
var arity = length;
var count = arguments.length;
var args = new Array(count);
var index = 0;
while (index < count) {
args[index] = arguments[index++];
}
args = a.concat(args);
return count < arity ?
partial(f, arity - count, args) :
f.apply(this, args);
};
}
</script>
However, it doesn't work for the following case:
// length :: [a] -> Number
function length(a) {
return a.length;
}
// filter :: (a -> Bool) -> [a] -> [a]
var filter = curry(function (f, a) {
return a.filter(f);
});
// compose :: (b -> c) -> (a -> b) -> a -> c
var compose = curry(function (f, g, x) {
return f(g(x));
});
// countWhere :: (a -> Bool) -> [a] -> Number
var countWhere = compose(compose(length), filter);
According to the following question countWhere
is defined as (length .) . filter
:
What does (f .) . g mean in Haskell?
Hence I should be able to use countWhere
as follows:
countWhere(odd, [1,2,3,4,5]);
function odd(n) {
return n % 2 === 1;
}
However, instead of returning 3
(the length of the array [1,3,5]
), it returns a function. What am I doing wrong?
@Aadit,
I'm posting this because you shared a comment on my answer to To “combine” functions in javascript in a functional way? I didn't specifically cover currying in that post because it's a very contentious topic and not really a can of worms I wanted to open there.
I'd be wary using the phrasing "how to correctly curry" when you seem to be adding your own sugar and conveniences into your implementation.
Anyway, all of that aside, I truly don't intend for this to be an argumentative/combative post. I'd like to be able to have an open, friendly discussion about currying in JavaScript while emphasizing some of the differences between our approaches.
Without further ado...
To clarify:
Given
f
is a function andf.length
isn
. Letcurry(f)
beg
. We callg
withm
arguments. What should happen? You say:Let's see a code example of what @Aadit M Shah's code actually does
There are two things happening here:
I don't believe there's a lot of room for debate here, but people seem to miss what currying actually is
I'm bolding that last bit, because it's so important; each function in the sequence only takes a single argument; not variadic (0, 1, or more) arguments like you suggest.
You mention haskell in your post, too, so I assume you know that Haskell has no such thing as functions that take more than one argument. (Note: a function that takes a tuple is still just a function that takes one argument, a single tuple). The reasons for this are profound and afford you a flexibility in expressiveness not afforded to you by functions with variadic arguments.
So let's re-ask that original question: What should happen?
Well, it's simple when each function only accepts 1 argument. At any time, if more than 1 argument is given, they're just dropped.
What happens when we call
id(1,2,3,4)
? Of course we only get the1
back and2,3,4
are completely disregarded. This is:curry
solutioncurrying technique à la naomik
In this approach, we write a curry function that continuously returns single-parameter functions until all arguments have been specified
As a result of this implementation we have 6 multi-purpose functions.
OK, so we've seen a
curry
technique that is implemented using a simple auxiliary loop. It has no dependencies and a declarative definition that is under 5 lines of code. It allows functions to be partially applied, 1 argument at a time, just as a curried function is supposed to work.No magic, no unforeseen auto-currying, no other unforeseen consequences.
But what really is the point of currying anyway?
Well, as it turns out, I don't really
curry
functions that I write. As you can see below, I generally define all of my reusable functions in curried form. So really, you only needcurry
when you want to interface with some functions that you don't have control over, perhaps coming from a lib or something; some of which might have variadic interfaces!I present
curryN
To curry or not to curry? That is the question
We'll write some examples where our functions are all in curried form. Functions will be kept extremely simple. Each with
1
parameter, and each with a single return expression.Your
countWhere
functionRemarks
So to curry or not to curry?
With ES6 arrow functions being the go-to choice for today's JavaScripter, I think the choice to manually curry your functions is a no-brainer. It's actually shorter and has less overhead to just write it out in curried form.
That said, you're still going to be interfacing with libs that do not offer curried forms of the functions they expose. For this situation, I'd recommend
curry
andcurryN
(defined above)partial
(as defined here)@Iven,
Your
curryN
implementation is very nice. This section exists solely for you.Apart from its mathematical definition
what impact has currying on programming? Abstraction over arity!
comp
expects two functionsf
andg
and a single arbitrary argumentx
. Consequentlyg
must be an unary function (a function with exactly one formal parameter) andf
too, since it is fed with the return value ofg
. It won't surprise anyone thatcomp(sqr)(inc)(1)
works.sqr
andinc
are both unary.But
mul
is obviously a binary function. How on earth is that going to work? Because currying abstracted the arity ofmul
. You can now probably imagine what a powerful feature currying is.In ES2015 we can pre-curry our functions with arrow functions succinctly:
Nevertheless, we need a programmatic curry function for all functions out of our control. Since we learned that currying primarily means abstraction over arity, our implementation must not depend on
Function.length
:Passing the arity explicitly to
curryN
has the nice side effect that we can curry variadic functions as well:One problem remains: Our curry solution can't deal with methods. OK, we can easily redefine methods that we need:
An alternative is to adapt
curryN
in a manner that it can handle both multi-argument functions and methods:I don't know if this is the correct way to curry functions (and methods) in Javascript though. It is rather one possible way.
EDIT:
naomik pointed out that by using a default value the internal API of the curry function is partially exposed. The achieved simplification of the curry function comes thus at the expense of its stability. To avoid API leaking we need a wrapper function. We can utilize the U combinator (similar to naomik's solution with Y):
Drawback: The implementation is harder to read and has a performance penalty.
The problem with your
curry
function (and for mostcurry
functions that people write in JavaScript) is that it doesn't handle extra arguments correctly.What
curry
doesSuppose
f
is a function andf.length
isn
. Letcurry(f)
beg
. We callg
withm
arguments. What should happen?m === 0
then just returng
.m < n
then partially applyf
to them
new arguments, and return a new curried function which accepts the remainingn - m
arguments.f
to them
arguments and return the result.This is what most
curry
functions do, and this is wrong. The first two cases are right, but the third case is wrong. Instead, it should be:m === 0
then just returng
.m < n
then partially applyf
to them
new arguments, and return a new curried function which accepts the remainingn - m
arguments.m === n
then applyf
to them
arguments. If the result is a function thencurry
the result. Finally, return the result.m > n
then applyf
to the firstn
arguments. If the result is a function thencurry
the result. Finally, apply the result to the remainingm - n
arguments and return the new result.The problem with most
curry
functionsConsider the following code:
If we use the incorrect
curry
functions, then this is equivalent to:However,
compose
only accepts three arguments. The last argument is dropped:Hence, the above expression evaluates to:
This further evaluates to:
The
compose
function expects one more argument which is why it returns a function instead of returning3
. To get the correct output you need to write:This is the reason why most
curry
functions are wrong.The solution using the correct
curry
functionConsider the following code again:
If we use the correct
curry
function, then this is equivalent to:Which evaluates to:
Which further evaluates to (skipping an intermediate step):
Which results in:
Producing the correct result
3
.The implementation of the correct
curry
functionNote that I'm not using
slice
to convert thearguments
object into an array because that is an optimization killer in V8:I am not sure how fast this implementation of
curry
is. Perhaps somebody could make it faster.Implications of using the correct
curry
functionUsing the correct
curry
function allows you to directly translate Haskell code into JavaScript. For example:The
id
function is useful because it allows you to partially apply a non-curried function easily:The
flip
function is useful because it allows you to easily create right sections in JavaScript:It also means that you don't need hacks like this extended
compose
function:What's a Good Name for this extended `compose` function?
You can simply write:
As mentioned in the question, if you want to compose
length
andfilter
then you use the(f .) . g
pattern:What does (f .) . g mean in Haskell?
Another solution is to create higher order
compose
functions:This is all possible because of the correct implementation of the
curry
function.Extra food for thought
I usually use the following
chain
function when I want to compose a chain of functions:This allows you to write code like:
Which is equivalent to the following Haskell code:
It also allows you to write code like:
Which is equivalent to the following Haskell code:
Hope that helps.