10

I wrote a simple curry function in JavaScript which works correctly for most cases:

const curry = (f, ...a) => a.length < f.length
    ? (...b) => curry(f, ...a, ...b)
    : f(...a);

const add = curry((a, b, c) => a + b + c);

const add2 = add(2);

const add5 = add2(3);

console.log(add5(5));

However, it doesn't work for the following case:

// length :: [a] -> Number
const length = a => a.length;

// filter :: (a -> Bool) -> [a] -> [a]
const filter = curry((f, a) => a.filter(f));

// compose :: (b -> c) -> (a -> b) -> a -> c
const compose = curry((f, g, x) => f(g(x)));

// countWhere :: (a -> Bool) -> [a] -> Number
const 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:

const odd = n => n % 2 === 1;

countWhere(odd, [1,2,3,4,5]);

However, instead of returning 3 (the length of the array [1,3,5]), it returns a function. What am I doing wrong?

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • Looks like `countWhere` is expected to return a Number, instead of `[1,3,5]`. – raine Jan 17 '15 at 16:54
  • Oops. Yes, you are correct. – Aadit M Shah Jan 17 '15 at 17:43
  • 4
    The "correctly" is subjective. The kind of currying that you're talking about in your answer is only relevant if you want to translate code from a language with automatic currying like Haskell and want to keep the translation similar. But if you want to use Javascript with a currying function then the version that you refer to as incorrect is actually correct -- when there are enough arguments you apply the function and you *don't* curry the result (ie, no auto-currying). This is even more important in a language with variadic functions like Javascript, see next comment. – Eli Barzilay Jan 17 '15 at 19:13
  • 5
    In the case of JS, you can't know if the function will use more arguments or not, so your correct version turn from a cute auto-currying thing into a broken tool. (Yes, I'm sure that you don't use variadic functions, but don't confuse random readers.) – Eli Barzilay Jan 17 '15 at 19:15
  • 1
    I have a strong case against variadic arguments: http://stackoverflow.com/a/26701110/783743 – Aadit M Shah Jan 17 '15 at 19:15
  • @EliBarzilay Yes, it is indeed subjective in a "subjective" language like JavaScript which has a lot of "subjective" features like variadic arguments. However, the fact remains that if I am forced to write `countWhere(odd)([1,2,3,4,5])` instead of `countWhere(odd, [1,2,3,4,5])` then that is plain wrong. Haskell has variadic functions too: http://stackoverflow.com/q/3467279/783743. Interestingly, these type-safe "variadic functions" work because of currying; and the "correct" version of `curry` allows them to be directly translated in JavaScript too. Hence, it actually enables variadic args. =) – Aadit M Shah Jan 17 '15 at 19:26
  • 4
    @AaditMShah, re variadic functions -- there is nothing wrong with them. See all lisps where they're very inherent and natural. Re Haskell, that's wrong -- Haskell has *only* unary functions, not even nullary ones (and the fact that many Haskellers are unaware of this is pretty amazing). Finally, the `countWhere` that you want is still a translation of a Haskell-ism, where a function can return another function and the whole thing is invoked as "one" function call (multiple ones, of course, due to currying), in a more JS-ish style, this wouldn't happen. – Eli Barzilay Jan 18 '15 at 01:22
  • 2
    So obviously, the question is whether it's a good idea to try and force this auto-curried-like view into JS code -- and the thing is that you'll always run into more subtle problems with more trickery that will be needed to go around them. For most people, that means that it's better to use JS like JS, or choose an auto curried language (Haskell or ML) and use it as such. If you like to keep on fighting, then that's your own choice -- and it's better to not confuse people into thinking that this is supposed to be better for JS programs. – Eli Barzilay Jan 18 '15 at 01:26
  • Even for your own choice, I'm guessing that what you really would feel better with is a language that either has auto currying, or can be tweaked to have it. – Eli Barzilay Jan 18 '15 at 01:27
  • 3
    Oh, and one last comment -- if you try this kind of thing in a statically-typed non-curried language (like Typed Racket, for example) you'll probably see that you're really changing the types of functions: when you curry a function F that returns a binary function G, the result will change to become curried itself, and therefore have a different type. That's a good way to see the essence of the fight you're having. – Eli Barzilay Jan 18 '15 at 01:31

4 Answers4

14

@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 and f.length is n. Let curry(f) be g. We call g with m arguments. What should happen? You say:

  1. If m === 0 then just return g.
  2. If m < n then partially apply f to the m new arguments, and return a new curried function which accepts the remaining n - m arguments.
  3. If m === n then apply f to the m arguments. If the result is a function then curry the result. Finally, return the result.
  4. If m > n then apply f to the first n arguments. If the result is a function then curry the result. Finally, apply the result to the remaining m - n arguments and return the new result.

Let's see a code example of what @Aadit M Shah's code actually does

var add = curry(function(x, y) {
  return function(a, b) {
    return x + y + a + b;
  }
});

var z = add(1, 2, 3);
console.log(z(4)); // 10

There are two things happening here:

  1. You're attempting to support calling curried functions with variadic arguments.
  2. You're automatically currying returned functions

I don't believe there's a lot of room for debate here, but people seem to miss what currying actually is

via: Wikipedia
In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument...

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.

function id(x) {
  return x;
}

What happens when we call id(1,2,3,4)? Of course we only get the 1 back and 2,3,4 are completely disregarded. This is:

  1. how JavaScript works
  2. how Wikipedia says currying should work
  3. how we should implement our own curry solution

Before we go further, I'm going to use ES6-style arrow functions but I will also include the ES5 equivalent at the bottom of this post. (Probably later tonight.)

another currying technique

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.

// no nonsense curry
const curry = f => {
  const aux = (n, xs) =>
    n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
  return aux (f.length, [])
}
   
// demo
let sum3 = curry(function(x,y,z) {
  return x + y + z;
});
    
console.log (sum3 (3) (5) (-1)); // 7

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 need curry 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

// the more versatile, curryN
const curryN = n => f => {
  const aux = (n, xs) =>
    n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
  return aux (n, [])
};

// curry derived from curryN
const curry = f => curryN (f.length) (f);

// some caveman function
let sumN = function() {
  return [].slice.call(arguments).reduce(function(a, b) {
    return a + b;
  });
};

// curry a fixed number of arguments
let g = curryN (5) (sumN);
console.log (g (1) (2) (3) (4) (5)); // 15

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.

// composing two functions
const comp = f => g => x => f (g (x))
const mod  = y => x => x % y
const eq   = y => x => x === y
const odd  = comp (eq (1)) (mod (2))

console.log (odd(1)) // true
console.log (odd(2)) // false

Your countWhere function

// comp :: (b -> c) -> (a -> b) -> (a -> c)
const comp = f => g => x =>
  f(g(x))

// mod :: Int -> Int -> Int
const mod = x => y =>
  y % x

// type Comparable = Number | String
// eq :: Comparable -> Comparable -> Boolean
const eq = x => y =>
  y === x

// odd :: Int -> Boolean
const odd =
  comp (eq(1)) (mod(2))

// reduce :: (b -> a -> b) -> b -> ([a]) -> b
const reduce = f => y => ([x,...xs]) =>
  x === undefined ? y : reduce (f) (f(y)(x)) (xs)

// filter :: (a -> Boolean) -> [a] -> [a]
const filter = f =>
  reduce (acc => x => f (x) ? [...acc,x] : acc) ([])

// length :: [a] -> Int
const length = x =>
  x.length

// countWhere :: (a -> Boolean) -> [a] -> Int
const countWhere = f =>
  comp (length) (filter(f));

console.log (countWhere (odd) ([1,2,3,4,5]))
// 3

Remarks

So to curry or not to curry?

// to curry
const add3 = curry((a, b, c) =>
  a + b + c
)

// not to curry
const add3 = a => b => c =>
 a + b + c

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 and curryN (defined above)
  • partial (as defined here)

@Iven,

Your curryN implementation is very nice. This section exists solely for you.

const U = f=> f (f)
const Y = U (h=> f=> f(x=> h (h) (f) (x)))

const curryN = Y (h=> xs=> n=> f=>
  n === 0 ? f(...xs) : x=> h ([...xs, x]) (n-1) (f)
) ([])

const curry = f=> curryN (f.length) (f)

const add3 = curry ((x,y,z)=> x + y + z)

console .log (add3 (3) (6) (9))
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • Hello @naomik. Yes, I agree that currying is about converting an `n`-ary function into `n` unary functions. That's the mathematical definition of currying and being a functional programmer I am well aware of it. I also agree that code like `add(1)(2)` would be the JavaScript equivalent of Haskell code (multiple function applications). However, there are two problems with such code. First, function application is expensive. Hence, `add(1, 2)` is faster than `add(1)(2)`. Second, code like `add(1)(2)` is uncouth. I would rather write `add(1, 2)` in JavaScript. – Aadit M Shah May 15 '15 at 02:33
  • Anyway, my question wasn't really about mathematical currying. That's set in stone and cannot be debated. My question was about most of the available `autoCurry` implementations in functional JavaScript libraries. I'd been using one of these libraries and I ran into a problem with their `autoCurry` implementations. Specifically, most functional JavaScript libraries run into the [following problem with "auto" currying which they try to solve using several variants of function composition](http://stackoverflow.com/q/17386706/783743). – Aadit M Shah May 15 '15 at 02:38
  • Finally, my implementation of `curry` not only avoids these common pitfalls but also allows one-to-one correspondence between functional programming languages like Haskell and JavaScript (not only including code like `add(1)(2)` but also code like `add(1, 2)` for efficiency). This allows you to write truly functional programs with having to resort to hacks employed by current functional libraries to work around their "incorrect" (in my humble opinion) implementations of `autoCurry`. Yes, the `curry` function is magic but it is restricted and it makes auto curried functions more mathematical. – Aadit M Shah May 15 '15 at 02:45
  • Anyway, you seem to like ES6 syntax and yes it is easy to define mathematically curried functions in ES6. However, not everyone is fond of ES6. In fact, I think ES6 syntax looks horrendous and hence I avoid it like the plague. There are two reasons why I prefer auto currying over writing mathematically curried functions. First, it is more efficient. Second, I don't have to write closures within closures. P.S. I'm not trying to build the next CoffeeScript. The only magic in my code is the `curry` function. Everything else is mathematical. Also, PureScript looks nice but is very inefficient. =) – Aadit M Shah May 15 '15 at 02:55
  • 2
    @AaditMShah I appreciate the response. Fwiw, the computer science definition of currying doesn't differ from the mathematical one. Best regards. – Mulan May 15 '15 at 04:55
  • Yes, it doesn't differ. However, the question is not about currying as that wouldn't be very useful in JavaScript. It's about auto currying. – Aadit M Shah May 15 '15 at 05:38
12

The problem with your curry function (and for most curry functions that people write in JavaScript) is that it doesn't handle extra arguments correctly.

What curry does

Suppose f is a function and f.length is n. Let curry(f) be g. We call g with m arguments. What should happen?

  1. If m === 0 then just return g.
  2. If m < n then partially apply f to the m new arguments, and return a new curried function which accepts the remaining n - m arguments.
  3. Otherwise apply f to the m 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:

  1. If m === 0 then just return g.
  2. If m < n then partially apply f to the m new arguments, and return a new curried function which accepts the remaining n - m arguments.
  3. If m === n then apply f to the m arguments. If the result is a function then curry the result. Finally, return the result.
  4. If m > n then apply f to the first n arguments. If the result is a function then curry the result. Finally, apply the result to the remaining m - n arguments and return the new result.

The problem with most curry functions

Consider the following code:

const countWhere = compose(compose(length), filter);

countWhere(odd, [1,2,3,4,5]);

If we use the incorrect curry functions, then this is equivalent to:

compose(compose(length), filter, odd, [1,2,3,4,5]);

However, compose only accepts three arguments. The last argument is dropped:

const compose = curry((f, g, x) =>f(g(x)));

Hence, the above expression evaluates to:

compose(length)(filter(odd));

This further evaluates to:

compose(length, filter(odd));

The compose function expects one more argument which is why it returns a function instead of returning 3. To get the correct output you need to write:

countWhere(odd)([1,2,3,4,5]);

This is the reason why most curry functions are wrong.

The solution using the correct curry function

Consider the following code again:

const countWhere = compose(compose(length), filter);

countWhere(odd, [1,2,3,4,5]);

If we use the correct curry function, then this is equivalent to:

compose(compose(length), filter, odd)([1,2,3,4,5]);

Which evaluates to:

compose(length)(filter(odd))([1,2,3,4,5]);

Which further evaluates to (skipping an intermediate step):

compose(length, filter(odd), [1,2,3,4,5]);

Which results in:

length(filter(odd, [1,2,3,4,5]));

Producing the correct result 3.

The implementation of the correct curry function

Implementing the correct curry function in ES6 is straightforward:

const curry = (f, ...a) => {
    const n = f.length, m = a.length;
    if (n === 0) return m > n ? f(...a) : f;
    if (m === n) return autocurry(f(...a));
    if (m < n) return (...b) => curry(f, ...a, ...b);
    return curry(f(...a.slice(0, n)), ...a.slice(n));
};

const autocurry = (x) => typeof x === "function" ? curry(x) : x;

Note that if the length of the input function is 0 then it's assumed to be curried.

Implications of using the correct curry function

Using the correct curry function allows you to directly translate Haskell code into JavaScript. For example:

const id = curry(a => a);

const flip = curry((f, x, y) => f(y, x));

The id function is useful because it allows you to partially apply a non-curried function easily:

const add = (a, b) => a + b;

const add2 = id(add, 2);

The flip function is useful because it allows you to easily create right sections in JavaScript:

const sub = (a, b) => a - b;

const sub2 = flip(sub, 2); // equivalent to (x - 2)

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:

const project = compose(map, pick);

As mentioned in the question, if you want to compose length and filter then you use the (f .) . g pattern:

What does (f .) . g mean in Haskell?

Another solution is to create higher order compose functions:

const compose2 = compose(compose, compose);

const countWhere = compose2(length, fitler);

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:

const chain = compose((a, x) => {
    var length = a.length;
    while (length > 0) x = a[--length](x);
    return x;
});

This allows you to write code like:

const inc = add(1);

const foo = chain([map(inc), filter(odd), take(5)]);

foo([1,2,3,4,5,6,7,8,9,10]); // [2,4,6]

Which is equivalent to the following Haskell code:

let foo = map (+1) . filter odd . take 5

foo [1,2,3,4,5,6,7,8,9,10]

It also allows you to write code like:

chain([map(inc), filter(odd), take(5)], [1,2,3,4,5,6,7,8,9,10]); // [2,4,6]

Which is equivalent to the following Haskell code:

map (+1) . filter odd . take 5 $ [1,2,3,4,5,6,7,8,9,10]

Hope that helps.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • 3
    This answer is not completely correct, see my comment to the question. (I'd comment here, but you seem to be having a dialog with yourself.) – Eli Barzilay Jan 17 '15 at 19:19
  • Regarding case 4. If the result of applying f to the first n arguments is not a function, what then? Is it considered an error or do you just discard the remaining arguments and return the non function value? – Ludwig Magnusson Sep 16 '15 at 07:00
  • 1
    @LudwigMagnusson It is an error because we still try to apply the non-function value to the remaining arguments. Hence, JavaScript will complain that you were trying to call a non-callable object. This is the desired behavior because we want an error every time we make such a stupid mistake. – Aadit M Shah Sep 16 '15 at 12:32
1

Apart from its mathematical definition

currying is the transformation of a function with n parameters into a sequence of n functions, which each accept a single parameter. The arity is thus transformed from n-ary to n * 1-ary

what impact has currying on programming? Abstraction over arity!

const comp = f => g => x => f(g(x));
const inc = x => x + 1;
const mul = y => x => x * y;
const sqr = x => mul(x)(x);

comp(sqr)(inc)(1); // 4
comp(mul)(inc)(1)(2); // 4

comp expects two functions f and g and a single arbitrary argument x. Consequently g must be an unary function (a function with exactly one formal parameter) and f too, since it is fed with the return value of g. It won't surprise anyone that comp(sqr)(inc)(1) works. sqr and inc are both unary.

But mul is obviously a binary function. How on earth is that going to work? Because currying abstracted the arity of mul. You can now probably imagine what a powerful feature currying is.

In ES2015 we can pre-curry our functions with arrow functions succinctly:

const map = (f, acc = []) => xs => xs.length > 0
 ? map(f, [...acc, f(xs[0])])(xs.slice(1))
 : acc;

map(x => x + 1)([1,2,3]); // [2,3,4]

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:

const curryN = (n, acc = []) => f => x => n > 1
 ? curryN(n - 1, [...acc, x])(f)
 : f(...acc, x);

const map = (f, xs) => xs.map(x => f(x));
curryN(2)(map)(x => x + 1)([1,2,3]); // [2,3,4]

Passing the arity explicitly to curryN has the nice side effect that we can curry variadic functions as well:

const sum = (...args) => args.reduce((acc, x) => acc + x, 0);
curryN(3)(sum)(1)(2)(3); // 6

One problem remains: Our curry solution can't deal with methods. OK, we can easily redefine methods that we need:

const concat = ys => xs => xs.concat(ys);
const append = x => concat([x]);

concat([4])([1,2,3]); // [1,2,3,4]
append([4])([1,2,3]); // [1,2,3,[4]]

An alternative is to adapt curryN in a manner that it can handle both multi-argument functions and methods:

const curryN = (n, acc = []) => f => x => n > 1 
 ? curryN(n - 1, [...acc, x])(f)
 : typeof f === "function"
  ? f(...acc, x)
  : x[f](...acc);

curryN(2)("concat")(4)([1,2,3]); // [1,2,3,4]

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

const U = f => f(f);
const curryN = U(h => acc => n => f => x => n > 1
 ? h(h)([...acc, x])(n-1)(f)
 : f(...acc, x))([]);

Drawback: The implementation is harder to read and has a performance penalty.

  • Very nice work, @Iven. I think your last implementation oversteps its own utility, but your original `curryN` is quite elegant. The readability is excellent. Now I'm trying to think of a way to write it that doesn't leak private api (`acc=[]`) and doesn't use a recursion combinator... – Mulan Jun 04 '16 at 07:26
  • @naomik Yes, `acc = []` is ugly. "_leak private api_" describes the problem well, btw. I haven't found a way to avoid this default-value-construct without using a wrapper. Provided I understand your curry implemented with `Y` correctly (see naomik's answer), you use such a wrapper with `Y (h=>...` - very ingenious solution! –  Jun 04 '16 at 13:13
-3
//---Currying refers to copying a function but with preset parameters

function multiply(a,b){return a*b};

var productOfSixNFiveSix = multiply.bind(this,6,5);

console.log(productOfSixNFive());

//The same can be done using apply() and call()

var productOfSixNFiveSix = multiply.call(this,6,5);

console.log(productOfSixNFive);

var productOfSixNFiveSix = multiply.apply(this,[6,5]);

console.log(productOfSixNFive);
Arijit Basu
  • 91
  • 1
  • 3