网络埋伏纪事

Master the JavaScript Interview: What is Function Composition?

网络埋伏纪事 · 2016-12-22推荐 · 114阅读 原文链接

Google Datacenter Pipes — Jorge Jorquera — (CC-BY-NC-ND-2.0)

“Master the JavaScript Interview” is a series of posts designed to prepare candidates for common questions they are likely to encounter when applying for a mid to senior-level JavaScript position. These are questions I frequently use in real interviews.

Functional programming is taking over the JavaScript world. Just a few years ago, few JavaScript programmers even knew what functional programming is, but every large application codebase I’ve seen in the past 3 years makes heavy use of functional programming ideas.

Function composition is the process of combining two or more functions to produce a new function. Composing functions together is like snapping together a series of pipes for our data to flow through.

Put simply, a composition of functions _f_ and _g can be defined as f(g(x)),_ which evaluates from the inside out — right to left. In other words, the evaluation order is:

  1. x

  2. g

  3. f

Let’s look at this more closely in code. Imagine you want to convert user’s full names to URL slugs to give each of your users a profile page. In order to do that, you need to walk through a series of steps:

  1. split the name into an array on spaces

  2. map the name to lower case

  3. join with dashes

  4. encode the URI component

Here’s a simple implementation:

Not bad… but what if I told you it could be more readable?

Imagine each of these operations had a corresponding composable function. This could be written as:

This looks even harder to read than our first attempt, but hang in there, this is going somewhere.

In order to accomplish this, we’re using composable forms of common utilities like split(), join() and map(). Here are the implementations:

With the exception of toLowerCase(), production-tested versions of all of these functions are available from Lodash/fp. You can import them like this:

import { curry, map, join, split } from 'lodash/fp';

Or like this:

const curry = require('lodash/fp/curry');
const map = require('lodash/fp/map');
//...

I’m being a little lazy here. Notice that this curry isn’t technically a real curry, which would always produce a unary function. Instead, it’s a simple partial application. See “What’s the Difference Between Curry and Partial Application?”, but for the purposes of this demonstration, it will work interchangeably with a real curry function.

Going back to our toSlug() implementation, there’s something that really bothers me about it:

That looks like a lot of nesting to me, and it’s a bit confusing to read. We can flatten the nesting with a function that will compose these functions for us automatically, meaning that it will take the output from one function and automatically patch it to the input of the next function until it spits out the final value.

Come to think of it, we have an array extras utility that sounds like it does something like that. It takes a list of values and applies a function to each of those values, accumulating a single result. The values themselves can be functions. The function is called reduce(), but to match the compose behavior above, we need it to reduce right to left, instead of left to right.

Good thing there’s a reduceRight() that does exactly what we’re looking for:

Like .reduce(), the array .reduceRight() method takes a reducer function and an initial value (_x). We iterate over the array functions (from right to left), applying each in turn to the accumulated value (v_).

With compose, we can rewrite our composition above without the nesting:

Of course, compose() comes with lodash/fp as well:

import { compose } from 'lodash/fp';

Or:

const compose = require('lodash/fp/compose');

Compose is great when you’re thinking in terms of the mathematical form of composition, inside out… but what if you want to think in terms of the sequence from left to right?

There’s another form commonly called pipe(). Lodash calls it flow():

Notice the implementation is exactly the same as compose(), except that we’re using .reduce() instead of .reduceRight(), which reduces left to right instead of right to left.

Let’s look at our toSlug() function implemented with pipe():

For me, this is much easier to read.

Hardcore functional programmers define their entire application in terms of function compositions. I use it frequently to eliminate the need for temporary variables. Look at the pipe() version of toSlug() carefully and you might notice something special.

In imperative programming, when you’re performing transformations on some variable, you’ll find references to the variable in each step of the transformation. The pipe() implementation above is written in a points-free style, which means that it does not identify the arguments on which it operates at all.

I frequently use pipes in things like unit tests and Redux state reducers to eliminate the need for intermediary variables which exist only to hold transient values between one operation and the next.

That may sound weird at first, but as you get practice with it, you’ll find that in functional programming, you’re working with very abstract, generalized functions in which the names of things don’t matter so much. Names just get in the way. You may start to think of variables as unnecessary boilerplate.

That said, I’m of the opinion that points-free style can be taken too far. It can become too dense, and harder to understand, but if you get confused, here’s a little tip… you can tap into the flow to trace what’s going on:

Here’s how you use it:

trace() is just a special form of the more general tap(), which lets you perform some action for each value that flows through the pipe. Get it? Pipe? Tap? You can write tap() like this:

Now you can see how trace() is just a special-cased tap():

You should be starting to get a sense of what functional programming is like, and how partial application & currying collaborate with function composition to help you write programs which are more readable with less boilerplate.


Members of “Learn JavaScript with Eric Elliott”, check out the new functional programming and Redux lessons. Be sure to watch the Shotgun series & ride shotgun with me while I build real apps with React and Redux.

相关文章