Consider this example:
Naming things
"If you have the name of a spirit, you have power over it." – Gerald Jay Sussman
Can you think of a better name for your exclude
function? I know I can. It's known as notEqual
. Simply knowing it as its true name makes it much more versatile when it comes to problem solving. "exclude" makes sense in the context of filtering an array, but somehow it makes less sense if we wanted to use the exclude
function elsewhere.
if (exclude(a,b))
console.log("a and b are not equal")
Functional programming is all about making functions as reusable as possible, so as we move forward, let's stick with
const notEqual = (x,y) => x !== y
Function.prototype.bind
Function.prototype.bind is used to bind values to function parameters. It's commonly used because it's been native since ECMAScript 5 – meaning you can accomplish your goal without adding any additional dependencies or making any changes to your existing code.
const notEqual = (x,y) => x !== y
const samples = ['foo', 'bar']
const foos = samples.filter(notEqual.bind(null, 'foo'))
console.log(foos) // ["bar"]
Partial Application
Partial application takes a function and some arguments and produces another function of smaller arity – arity is a fancy word for "the number of arguments a function takes"
Now that you're familiar with Function.prototype.bind
, you already know partial application. The only difference is bind
forces you to provide the context of a binding. Contexts are a bother in most functional programs, so sometimes it's easier to have a function that lets us partially apply without concerning ourselves with context.
const partial = (f, ...xs) => (...ys) => f(...xs, ...ys)
const notEqual = (x,y) => x !== y
const samples = ['foo', 'bar']
const foos = samples.filter(partial(notEqual, 'foo'))
console.log(foos) // ["bar"]
Currying
Currying, while similar to partial application, is another way to approach your problem. Currying takes a function of multiple arguments and transforms it into a sequence of unary functions – functions that take one argument each.
const notEqual = (x,y) => x !== y
const curry = f => x => y => f(x,y)
const samples = ['foo', 'bar']
const foos = samples.filter(curry(notEqual)('foo'))
console.log(foos) // ["bar"]
If you're having trouble seeing how this is different than partial application, note you won't see much of a difference until function arity is greater than two – See also: contrast currying with partial application.
As you can see, readability is starting to suffer a little bit. Instead of currying on the fly, if notEqual
is under our control, we could define it in curried form from the start
const notEqual = x => y => x !== y
const samples = ['foo', 'bar']
const foos = samples.filter(notEqual('foo'))
console.log(foos) // ["bar"]
You may not have even noticed it, but partial
(above) is defined in curried style!
Related: "What do multiple arrow functions mean in JavaScript?"
Currying is a massively powerful concept and useful in a wide variety of ways. You might say it's overkill for solving this single, isolated problem, and you'd be right. You'll really only start to see the benefits of currying when it is widely used in a program or language as it has a systemic effect – and ultimately, it provides abstraction over function arity itself.
const apply = f => x => f (x)
const notEqual = x => y => x !== y
const filter = f => xs => xs.filter(apply(f))
const notFoo = filter(notEqual('foo'))
const samples = ['foo', 'bar']
console.log(notFoo(samples)); // ["bar"]
Final Remarks
There's a lot of options available to you and you might be wondering which is the "correct" one to choose. If you're looking for a silver bullet, you'll be sad to learn there isn't one. As with everything there are trade-offs.
I find partial/procedural application to be an indispensable tool, and therefore I try to write all of my JavaScript functions in fully curried form. That way I avoid dropping calls to partial
and curry
all over my program. The consequence of this is the code ends up looking a little foreign, at first – comparison functor • round-robin • make anything you want • higher-order generators and DIY iterators • id generator • generic function repetition • merge/flatten array • custom iteration
Not all parts of your programs are fully under your control tho, right? Of course you're probably using some external dependencies and it's unlikely that they're going to have the perfect functional interface you're looking for. In such a case, you'll end up using partial
and curry
to interface with other code that you cannot change.
Lastly, look at some of the functional libraries out there like folktalke or Ramda. I don't recommend either for beginner functional programmers, but something worth looking into after you cut your teeth.