Currying is not idiomatic in JavaScript

November 25, 2017 0 Comments

Currying is not idiomatic in JavaScript

 

 

Update 2017-11-18a: I worded a few things more carefully, to make it clear that I don’t hate functional programming (I’m a fan).

Update 2017-11-18b: I added subsections to Sect. “Currying is in conflict with some of JavaScript’s foundations”.

In this blog post I explain why, in my opinion, currying is in conflict with some of JavaScript’s foundations.

Recommended reading if you are not sure what currying and partial application mean (and what the difference is between them): “Currying versus partial application

How do you mean “not idiomatic”?  #

The slightly agressive title may lead some people to misread the tone of this blog post. So let me be clear: I love functional programming. My main points are:

  • A few core JavaScript features are in conflict with currying.
  • There are alternatives to currying (especially an upcoming proposal) that, in my opinion, are superior when it comes to performing partial application. These are not in conflict.

One of JavaScript’s best traits is how many different styles of programming it can accomodate. Thus: if you like currying a lot and none of the mentioned alternatives work for you, feel free to use currying. If you do so, be consistent within your own code. And be prepared for your code being slightly at odds with the rest of the ecosystem.

Currying  #

Currying is a popular technique in functional programming. It helps with partial application.

The idea is as follows: If you don’t provide all parameters for a function, it returns a function whose input are the remaining parameters and whose output is the result of the original function.

If, for example, you wanted to add 2 to all elements of an Array and had a binary function add(x, y), you could do it as follows:

const arr = [1, 2, 3];
arr.map(add(2)); 

As an aside, functional programming languages with automatic currying often have plus operators that can be used this way.

You have two options for implementing add() in a way that supports currying.

Simple currying  #

ES6 arrow functions make it easy to write curried functions manually:

const add = x => y => x + y;

That means that you have to invoke add() as follows:

add(2)(3); 

This is currying: a function with an arity greater than one is transformed into a nested series of functions. Most functional programming languages with automatic currying have syntax where there is no difference between add(1, 2) and add(1)(2).

Overloaded currying  #

Some libraries provide functions that are overloaded. Each of those overloaded functions behaves differently depending on the number of parameters you provide:

  • If you only provide a single parameter, it works as if it were curried.
  • If you provide all parameters, it works like a normal function call.
  • If you provide more than one parameter (and not all parameters), it returns a function that is bound to those parameters.

This style of currying can be implemented as follows.

function add(...args) { if (args.length < 2) { return add.bind(this, ...args); } const [x, y] = args; return x + y;
}

Alternatively, one could write a helper function that transforms normal functions to this style.

Currying is a technique for transforming functions so that they help with partial application.

Currying is in conflict with some of JavaScript’s foundations  #

If you want currying in JavaScript, you therefore have two options:

  • Use real currying.
    • Pro: easier to type statically.
    • Con: non-idiomatic syntax for function calls.


  • Use overloaded currying.

    • Pro: nicer syntax.
    • Con: harder to get static types right.


Then you are faced with a few disadvantages that I’ll explain next. I’m assuming that you’ll transform some built-in functions and methods to suit your style of currying.

Note: not all of these disadvantages are equally important.

Named parameters  #

In JavaScript, named parameters can be simulated via object literals (details: section “Simulating named parameters” in “Exploring ES6”). I like this way of handling parameters a lot, because it leads to code that is much more self-descriptive. Not all JavaScript libraries for currying support named parameters.

Note: languages that support both built-in currying and named/labeled parameters don’t have this issue. For example: ReasonML.

Normally, if you put parentheses behind a function, you execute that function. With currying, you have to know the arity of a function foo in order to know whether foo(123) calls that function or returns a partially applied version of it.

Compare that to two alternatives for doing partial application (described later):

foo(123, ?)
_ => foo(123, _)

You can see clearly that foo() has the arity 2 and that one more value still needs to be provided.

With named parameters, you lose even more information if you curry. Compare:

arr.map(findCity({ latitude: ‎48.137154 })); arr.map(_ => findCity({ latitude: ‎48.137154, longitude: _ }))

Static type systems (TypeScript, Flow, ...) prevent you from making mistakes here, but the alternatives are still easier to read.

Currying clashes with parameter default values  #

Parameter default values tell functions what values to use if parameters are omitted. For example:

function add(x=0, y=0) { return x + y;
}

In this case, parameter default values are not terribly useful. But in general, they enable you to add parameters transparently – without breaking existing callers. This technique is especially useful with named parameters and I have used it a few times to keep functions and method as generally applicable as possible.

With parameter default values, omitting a parameter means using the default. With currying, omitting a parameter means returning a partially applied function. Therefore, the two are in conflict.

Currying-friendly functions  #

With currying, it’s important that the parameters of functions start with options and end with the main operand. JavaScript functions tend not to do that.

For example, the signature of parseInt() is:

parseInt(s : string, radix : number)

That makes it impossible to prefill the radix like this:

['32', '17', '5'].map(parseInt(10)) 

What alternatives are there for partial application?  #

When it comes to currying, what people usually really want is partial application. Let’s take the following example of overloaded currying and explore alternatives.

[1, 2, 3].map(add(2))

Alternative 1: .bind().

[1, 2, 3].map(add.bind(null, 2))

Alternative 2: arrow functions.

[1, 2, 3].map(x => add(2, x))

Alternative 3: an upcoming proposal for partial application.

[1, 2, 3].map(add(2, ?))

Note: the syntax is still in flux and the proposal is at an early stage.

The nice thing about this proposal is that it works well with arbitrary signatures and has a very descriptive syntax.

Further reading  #


Tag cloud