• Sometimes Bias is Holding You Back: It’s Time to Embrace Arrow Functions
  • By Eric Elliott
  • The Nuggets translation Project
  • Translator: lsvih
  • Proofreader: Germxu,GangsterHyj

Don’t let your preference get in the way: embrace the arrow function!

CC BY-NC-ND 2.0 CC BY-NC-ND 2.0

I teach JavaScript for a living. I recently taught a class on the Currization of arrow functions to my students — this is just the beginning of the class. I thought it was a good skill to use, so I brought it up early in the course. The students didn’t let me down and learned to use the arrow function for Currization much faster than I expected.

If students can understand it and benefit from it as soon as possible, why not teach them the arrow function earlier?

Note: My course is not for those who have never worked with code before. Most students have at least a few months of programming experience before they join our program — whether they’re self-taught, trained, or professional in their own right. However, I’ve found that many young developers with little or no experience are quick to embrace these themes.

I’ve seen a lot of students become proficient in using arrow functions after an hour of class. (If you’re in the “Learn JavaScript with Eric Elliott” class, you can watch this 55-minute video on The Currization and Composition of ES6.)

Seeing how quickly the students mastered and applied their newly discovered Currization method reminded me of my Twitter post about currization arrow functions and the “unreadable” response from a bunch of people. I wonder why they insist on it.

First, let’s take a look at this example. I tweeted about this function, and I found a backlash against it:

const secret = msg= > () => msg;Copy the code

I find it incredible that someone on Twitter has accused me of misleading people. I wrote this function to demonstrate how easy it is to write coriolization functions in ES6. It is the simplest practical use of closure expressions in JavaScript THAT I can think of. (Related reading: What are closures?)

It is equivalent to the following function expression:

const secret = function (msg) {
  return function () {
    return msg;
  };
};Copy the code

Secret () is a function that takes MSG and returns a new function that will return the value of MSG. Whatever value you pass into secret(), it fixes the MSG value with a closure.

You can use it like this:

const mySecret = secret('hi');
mySecret(); // 'hi'Copy the code

As it turns out, the double arrows don’t confuse people. I firmly believe that:

For those familiar with it, the single-line arrow function is the most readable way to express currified functions in JavaScript.

A lot of people have criticized me, telling me to write long code is easier to read than short code. Sometimes they may be right, but most of the time they are wrong. Longer, more detailed code is not necessarily easier to read — at least, for those familiar with arrow functions.

The people I see on Twitter who disagree don’t enjoy the smooth learning of arrow functions that my students do. In my experience, students learn currified arrow functions like fish living in water. After only a few days, they started using arrows. It helps students navigate programming problems with ease.

I don’t see any “difficulty” in learning, reading, and understanding arrow functions for those students who, once they decide to learn, can basically master it in an hour or so of class.

They could easily read currified arrow functions, and even though they had never seen anything like it, they were able to tell me what the functions did. And when I gave them tasks they were very comfortable doing them on their own.

On the other hand, they were able to get familiar with the Currified arrow function quickly and had no problems with it. They read these functions like you would read a sentence, and their understanding of them led them to write simpler, less buggy code.

Why do some people think traditional function expressions look “more readable”?

Preference is a significant human cognitive bias that leads us to make self-defeating choices when better alternatives are available. We ignore the more comfortable and better method and habitually go back to the old method we used before.

You can read more about The psychology of favouritism in this book: The Undoing Project: A Friendship That Changed Our Minds. (In many cases, we deceive ourselves.) Every software engineer should read this book because it will encourage you to think critically and experiment with your hypotheses to avoid falling into cognitive traps. The stories of discovering cognitive traps are also interesting.

Traditional function expressions can cause bugs in your code

Today I rewrote a Currized arrow function written in ES6 using ES5 syntax in order to release open source modules that people can use in older browsers without compiling them. The ES5 version, however, shocked me.

The ES6 version of the code is very short, brief, and elegant — only four lines.

I thought it would be a good idea to tweet that the arrow function is a superior implementation, and that it’s time to abandon the traditional way of writing function expressions as well as your bad habit.

So I sent out a tweet:

In case you can’t see the image clearly, paste the text of this function below:

// Use the arrow function currize
const composeMixins = (. mixins) = > (
  instance = {},
  mix = (. fns) = > x => fns.reduce((acc, fn) = >fn(acc), x) ) => mix(... mixins)(instance);// Compare ES5 style code:
var composeMixins = function () {
  var mixins = [].slice.call(arguments);
  return function (instance, mix) {
    if(! instance) instance = {};if(! mix) { mix =function () {
        var fns = [].slice.call(arguments);
        return function (x) {
          return fns.reduce(function (acc, fn) {
            return fn(acc);
          }, x);
        };
      };
    }
    return mix.apply(null, mixins)(instance);
  };
};Copy the code

The function here encapsulates a pipe(), which is a standard functional programming utility function commonly used for composite functions. The pipe() function is lodash/flow in Lodash and r.ipe () in Ramda, and is even an operation symbol in itself in some functional programming languages.

Everyone familiar with functional programming should be familiar with it. Its implementation mainly relies on Reduce.

In this case, it’s used to combine mixing functions, but that’s irrelevant (there are blog posts on that). There are a few important details to note:

This function can mix any number of functions and return a function that applies other functions in a pipe — like a pipeline. Each blending function takes an instance as input and then passes in some variables before passing itself to the next function in the pipeline.

If you don’t pass instance, it will create a new object for you.

Sometimes you might want to mix it in a different way. For example, use compose() instead of pipe() to pass the function, reversing the composition order.

If you don’t need to customize the behavior of functions when mixing, you can simply use the default Settings and use pipe() to complete the process.

FACTS

Beyond the readability distinction, here are some objective facts relevant to this example:

  • I have many years of ES5 and ES6 programming experience, whether arrow function expressions or other function expressions I am familiar with. So favoritism is not a fickle factor for me.
  • I wrote the ES6 version of the code in a matter of seconds, and it was bug-free (it passed all the unit tests, so I’m sure of that).
  • Writing the ES5 version of the code took me a few minutes. A few seconds, a few minutes, that’s a big difference. I got the scope of a function wrong twice while writing ES5 code; Write out 3 bugs and spend time debugging and fixing them separately; And two other times I had to use itconsole.log()To figure out what’s going on.
  • The ES6 version is just four lines long.
  • The ES5 version has 21 lines of code (17 lines of actual code).
  • Although the ES5 code is more verbose, it still lacks some information compared to the ES6 code. It’s longer, but it says less. This question will be addressed later.
  • The ES6 version code has two speard operators in the code. The ES5 version code does not have this operator, but uses itMeaning obscuretheargumentsObject, which severely affects the readability of function content. (One reason not recommended)
  • The ES6 version code is defined in the function fragmentmixYou can clearly see that it is the value of the parameter. The ES5 code obfuscates this detail by hiding it in the function body. (Reason 2 not recommended)
  • The ES6 version has only two layers of code blocks, which will help readers understand the structure of the code and how to read it. ES5 code has six layers of code blocks, and the complex hierarchy makes the readability of function structures very poor. (Reason no. 3)

Pipe () takes up most of the body of the function in the ES5 version code — it’s a ridiculous idea to put them all on the same line. It is necessary to separate out the pipe() function to make our ES5 code more readable:

var pipe = function () {
  var fns = [].slice.call(arguments);

  return function (x) {
    return fns.reduce(function (acc, fn) {
      return fn(acc);
    }, x);
  };
};

var composeMixins = function () {
  var mixins = [].slice.call(arguments);

  return function (instance, mix) {
    if(! instance) instance = {};if(! mix) mix = pipe;return mix.apply(null, mixins)(instance);
  };
};Copy the code

This way, I find it more readable and easier to understand.

Let’s see what happens if we make some readability “optimizations” to the ES6 version code:

const pipe = (. fns) = > x => fns.reduce((acc, fn) = > fn(acc), x);

const composeMixins = (. mixins) = >( instance = {}, mix = pipe ) => mix(... mixins)(instance);Copy the code

Like the ES5 version of the code, this “optimized” code is much more verbose (it adds new variables that weren’t there before). Unlike the ES5 code, this version does not significantly improve code readability by abstracting the concept of pipes. After all, the variable mix is clearly stated in the function, so it’s easier to understand.

The definition of mix itself is already there on one line, and it’s unlikely that anyone reading the code will lose track of when to end the mix and when the rest of the code executes.

Now we have two variables representing the same thing. Do we benefit from it? Not at all.

So why do ES5 functions become more readable when they are abstracted from functions?

Because the previous ES5 version of the code was significantly more complex. The source of this complexity is the focus of our discussion. I can assert that its complexity boils down to syntactic interference, which does nothing but obscure the meaning of the function itself.

Let’s take a different approach and get rid of some of the superfluous variables, using ES6 code in both examples and only comparing arrow functions with traditional function expressions:

var composeMixins = function (. mixins) {
  return function (instance = {}, mix = function (... fns) {
      return function (x) {
        return fns.reduce(function (acc, fn) {
          returnfn(acc); }, x); }; {})returnmix(... mixins)(instance); }; };Copy the code

Now, at least I think it’s significantly more readable. We modified it with rest syntax and default parameter syntax. Of course, you need to be familiar with rest syntax and default parameter syntax to find this version of the code more readable. But even if you don’t know that, I think this version will look more organized.

It’s improved a lot, but I think this version is still pretty neat. It might be helpful to abstract pipe() out and write it into its own function:

const pipe = function (. fns) {
  return function (x) {
    return fns.reduce(function (acc, fn) {
      return fn(acc);
    }, x);
  };
};

// Traditional function expressions
const composeMixins = function (. mixins) {
  return function (instance = {}, mix = pipe) {
    returnmix(... mixins)(instance); }; };Copy the code

Isn’t that better? Now mix is a separate one, and the function structure is much clearer – but that’s not to my taste, it’s too syntactically intrusive. In the current composeMixins(), I don’t think it’s clear enough to describe where one function ends and the other starts.

The funcion keyword seems to be mixed up with other code except for the calling function body. The true functionality of my function is hidden! Where does the argument call and function body start? I can analyze it if I look closely, but it’s really not easy for me to read.

So what if we removed the function keyword and replaced the return keyword with a big arrow => pointing to the return value to avoid it getting mixed up with other key parts?

Of course we could, the code would look like this:

const composeMixins = (. mixins) = >( instance = {}, mix = pipe ) => mix(... mixins)(instance);Copy the code

It should now be clear what this code does. ComposeMixins () is a function that passes in any number of mixins and eventually returns a function that takes two additional arguments (instance and mix). It returns the result of instance composed through the mixins pipeline.

One more thing… If we do the same optimization for pipe(), we can magically write it on one line:

const pipe = (. fns) = > x => fns.reduce((acc, fn) = > fn(acc), x);Copy the code

When it is defined on a single line, it becomes less straightforward to abstract it as a function.

Also keep in mind that this function is useful in Lodash, Ramda, and other libraries, but it’s not worth it to import those libraries just to use this function.

So why is it necessary to write a line of my own? There should be. It’s actually two different functions, and separating them makes the code clearer.

On the other hand, if you write it on a single line, when you look at the parameter name, you already know its type and use case. We write it on one line, as shown in the following code:

const composeMixins = (. mixins) = > (
  instance = {},
  mix = (. fns) = > x => fns.reduce((acc, fn) = >fn(acc), x) ) => mix(... mixins)(instance);Copy the code

Now let’s go back to the original function. No matter what adjustments we made later, we didn’t throw away any information we already had. Also, by declaring variables and default values in the line, we add information to the function, describing how it is used and what parameter values look like.

The extra code added in the ES5 release is all syntactic interference. This code is of no use to anyone familiar with the Currified arrow function.

Once you’re familiar with the Currified arrow function, you’ll find the initial code clearer and more readable because there’s no extra syntax to fool around with.

The Currified arrow function also reduces the number of hiding places for bugs because it allows fewer parts of the bug to hide. I suspect there are a lot of bugs hidden in traditional function expressions that can be found and eliminated once you upgrade to using arrow functions.

I hope your team will also support, learn and apply ES6’s cleaner code style to increase productivity.

Sometimes it’s the right thing to do to go into detail in code, but in general, less code is better. If less code can achieve the same thing, communicate more information without throwing anything away, then it’s significantly superior. The key to recognizing these differences is to look at the information they convey. If the added code doesn’t make more sense, it shouldn’t exist. It’s as simple as the stylistic conventions of natural languages (no nonsense). Apply this presentation style specification to your code. Embrace it, and you’ll write better code.

As the day wore on and it was dark, there were still other tweets saying that the ES6 version of the code was even less readable:

Suffice it to say: it’s time to get comfortable with ES6, Currization, and combinatorial functions.

The next step

“Learn JavaScript with Eric Elliott” members can now watch this approximately 55-minute video course on ES6 Corrification and Composition.

If you are not one of our members, you will miss this opportunity.

Author’s brief introduction

Eric Elliott is the author of the “Programming JavaScript Applications” book and the “Learn JavaScript with Eric Elliott” course published by O’Reilly. He has helped with software development for Adobe, Lemmy, The Wall Street Journal, ESPN, BBC and websites for musicians such as Usher, Frank Ocean, and Metallica.

Finally feed the dog:

He spent his life in the Bay Area with the most beautiful woman in the world.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. Android, iOS, React, front end, back end, product, design, etc. Keep an eye on the Nuggets Translation project for more quality translations.