I Don’t Hate Arrow Functions

By  on  

TL;DR

Arrow functions are fine for certain usages, but they have so many variations that they need to be carefully controlled to not break down the readability of the code.

While arrow functions clearly have a ubiquitous community consensus (though not unanimous support!), it turns out there's a wide variety of opinions on what makes "good" usage of => and not.

Configurable linter rules are the best solution to wrangling the variety and disagreement of arrow functions.

I released proper-arrows ESLint plugin with several configurable rules to control => arrow functions in your code base.

Opinions are like noses...

Anyone who's followed me (tweets, books, courses, etc) for very long knows that I have lots of opinions. In fact, that's the only thing I'm an expert on -- my own opinions -- and I'm never at a loss for them!

I don't subscribe to the "strong opinions, loosely held" mantra. I don't "loosely hold" my opinions because I don't see any point in having an opinion if there isn't sufficient reason for that opinion. I spend a lot of time researching and tinkering and writing and trying out ideas before I form an opinion that I would share publicly. By that point, my opinion is pretty strongly held, by necessity.

What's more, I teach based on these opinions -- thousands of developers in different companies all over the world -- which affords me the opportunity to deeply vet my opinions through myriad discussion and debate. I'm tremendously privleged to be in such a position.

That doesn't mean I can't or won't change my opinions. As a matter of fact, one of my most strongly held opinions -- that JS types and coercion are useful in JS -- has been shifting lately, to a fairly significant degree. I have a much more rounded and deepened perspective on JS types and why type-aware tooling can be useful. And even my opinion on => arrow functions, the punchline of this article, has evolved and deepened.

But one of the things many people tell me they appreciate about me is, I don't just state opinions, I back those opinions up with careful, thought-out reasoning. Even when people vehemently disagree with my opinions, they often compliment me on at least owning those opinions with backing.

And I try to inspire the same in others through my speaking, teaching, and writing. I don't care if you agree with me, I only care that you know why you have an technical opinion and can earnestly defend it with your own line of reasoning. To me, that's a healthy relationship with technology.

Arrow Functions != functions

It is my sincere belief that the => arrow function is not suitable as a general purpose replacement for all (or even most) function functions in your JS code. I genuinely don't find them more readable in most cases. And I'm not alone. Any time I share an opinion like that on social media, I often get dozens of "me too!" responses peppered in with the scores of "you're totally wrong!" responses.

But I'm not here to rehash the entire debate over => arrow functions. I've written extensively about my opinions on them, including these sections in my books:

Whatever your preferences around =>, to suggest that it's only a better function is to be plainly reductive. It's a far more nuanced topic than just a one-to-one correspondence.

There are things to like about =>. You might find that surprising for me to say, since most people seem to assume I hate arrow functions.

I don't (hate them). I think there are definitely some important benefits.

It's just that I don't unreservedly endorse them as the new function. And these days, most people aren't interested in nuanced opinions in the middle. So since I'm not entirely in the pro-=> camp, I must be entirely in the opposition camp. Not true.

What I hate is suggesting they're universally more readable, or that they're objectively better in basically all cases.

The reason I reject this stance is because I REALLY DO STRUGGLE TO READ THEM in many cases. So that perspective just makes me feel dumb/inferior as a developer. "There must be something wrong with me, since I don't think it's more readable. Why do I suck so much at this?" And I'm not the only one whose impostor syndrome is seriously stoked by such absolutes.

And the cherry on top is when people tell you that the only reason you don't understand or like => is because you haven't learned them or used them enough. Oh, right, thanks for the (condescending) reminder it's due to my ignorance and inexperience. SMH. I've written and read literally thousands of =>functions. I'm quite certain I know enough about them to hold the opinions I have.

I'm not in the pro-=> camp, but I recognize that some really do prefer them, legitimately. I recognize that some people come to JS from languages that have used => and so they feel and read quite natural. I recognize that some prefer their resemblance to mathematical notation.

What's problematic IMO is when some in those camps simply cannot understand or empathize with dissenting opinions, as if there must just be something wrong with them.

Readability != Writability

I also don't think you know what you're talking about when you talk about code readability. By and large, the vast majority of opinions on code readability, when you break them down, are based on a personal stance about preferences in writingconcise code.

When I push back in debates about code readability, some just dig in their heels and refuse to support their opinion. Others will waive off the concerns with, "readability is all just subjective anyway".

The flimsiness of that response is stunning: two seconds ago they were vehemently claiming => arrow is absolutely and objectively more readable, and then when pressed, they admit, "well, I think it's more readable, even if ignorants like you don't."

Guess what? Readability is subjective, but not entirely so. It's a really complex topic. And there are some who are undertaking to formally study the topic of code readability, to try to find what parts of it are objective and what parts are subjective.

I have read a fair amount of such research, and I'm convinced that it's a complicated enough topic that it can't be reduced to a slogan on a t-shirt. If you want to read them, I would encourage you doing some google searching and reading of your own.

While I don't have all the answers myself, one thing I'm certain about is, code is more often read than written, so perspectives on the topic which ultimately come from "it's easier/quicker to write" don't hold much standing. What needs to be considered is, not how much time do you save writing, but how clearly will the reader (future you or someone else on the team) be able to understand? And ideally, can they mostly understand it without pouring over the code with a fine-toothed comb?

Any attempt to justify writability affordances with unsubstantiated claims about readability benefits is a weak argument at best, and in general, nothing but a distraction.

So I roundly reject that => is always and objectively "more readable".

But I still don't hate arrow functions. I just think to use them effectively, we need to be more disciplined.

Linters == Discipline

You might be of the (incorrect) belief that linters tell you objective facts about your code. They can do that, but that's not their primary purpose.

The tool that's best suited to tell you if your code is valid is a compiler (ie, the JS engine). The tool that's best suited to tell you whether your code is "correct" (does what you want it to do) is your test suite.

But the tool that's best suited to tell you if your code is appropriate is a linter. Linters are opinionated collections of rules about how you should style and structure your code, so as to avoid likely problems -- according to the authors of those opinion-based rules.

That's what they're for: to apply opinions to your code.

That means it's almost certain that these opinions will, at one time or another, "offend" you. If you're like most of us, you fancy yourself pretty good at what you do, and you know that this thing you're doing on this line of code is right. And then the linter pops up and says, "Nope, don't do it that way."

If your first instinct is sometimes to disagree, then you're like the rest of us! We get emotionally attached to our own perspectives and abilities, and when a tool tells us we're wrong, we chuff a little bit.

I don't get mad at the test suite or the JS engine. Those things are all reporting facts about my code. But I can definitely get irritated when the linter's opinion disagrees with mine.

I have this one linter rule that I enabled a few weeks ago, because I had an inconsistency in my coding that was annoying me on code re-reads. But now this lint rule is popping up two or three times an hour, nagging me like a stereotypical grandma on a 90's sitcom. Every single time, I ponder (for just a moment) if I should just go disable that rule. I leave it on, but to my chagrin.

So why subject ourselves to this torment!? Because linter tools and their opinions are what give us discipline. They help us collaborate with others.

They ultimately help us communicate more clearly in code.

Why shouldn't we let every developer make their own decisions? Because of our tendency toward emotional attachment. While we're in the trenches working on our own code, against unreasonable pressure and deadlines, we're in the least trustable mindset to be making those judgement calls.

We should be submitting to tools to help us maintain our discipline.

It's similar to how TDD advocates submit to the discipline of writing tests first, in a formal set of steps. The discipline and the bigger picture outcome of the process are what we value most, when we're level headed enough to make that analysis. We don't institute that kind of process when our code is hopelessly broken and we have no idea why and we're just resorting to trying random code changes to see if they fix it!

No. If we're being reasonable, we admit that the overall good is best served when we set up reasonable guidelines and then follow the discipline of adhering to them.

Configurability Is King

If you're going to knowingly subject yourself to this finger wagging, you (and your team, if applicable) are certainly going to want some say-so in what rules you're required to play by. Arbitrary and unassailable opinions are the worst kind.

Remember the JSLint days when 98% of the rules were just Crockford's opinions, and you either used the tool or you didn't? He straight up warned you in the README that you were going to be offended, and that you should just get over it. That was fun, right? (Some of you may still be using JSLint, but I think you should consider moving on to a more modern tool!)

That's why ESLint is king of the linters these days. The philosophy is, basically, let everything be configurable. Let developers and teams democratically decide which opinions they all want to submit to, for their own discipline and good.

That doesn't mean every developer picks their own rules. The purpose of rules is to conform code to a reasonable compromise, a "centralized standard", that has the best chance of communicating most clearly to the most developers on the team.

But no rule is ever 100% perfect. There's always exception cases. Which is why having the option to disable or re-configure a rule with an inline comment, for example, is not just a tiny detail but a critical feature.

You don't want a developer to just have their own local ESLint config that overrides rules while they commit code. What you want is for a developer to either follow the established rules (preferred!) OR to make an exception to the rules that is clear and obvious right at the point where the exception is being made.

Ideally, during a code review, that exception can be discussed and debated and vetted. Maybe it was justified, maybe it wasn't. But at least it was obvious, and at least it was possible to be discussed in the first place.

Configurability of tools is how we make tools work for us instead of us working for the tools.

Some prefer convention-based approaches to tooling, where the rules are pre-determined so there's no discussion or debate. I'm know that works for some developers and for some teams, but I don't think it is a sustainable approach for generalized, broad application. Ultimately, a tool that is inflexible to the changing project needs and DNA of the developer(s) using it, will end up falling into obscurity and eventually replaced.

Proper Arrows

I fully recognize my usage of the the word "proper" here is going to ruffle some feathers. "Who is getify to say what is proper and not?"

Remember, I'm not trying to tell you what is proper. I'm trying to get you to embrace the idea that opinions about => arrow functions are as varied as all the nuances of their syntax and usage, and that ultimately what is most appropriate is that some set of opinions, no matter what they are, should be applicable.

While I'm a big fan of ESLint, I've been disappointed by the lack of support from built-in ESLint rules for controlling various aspects of => arrow functions. There are a few built-in rules, but I'm frustrated that they seem to focus mostly on superficial stylistic details like whitespace.

I think there are a number of aspects that can hamper => arrow function readability, issues that go way beyond what the current ESLint ruleset can control. I asked around on twitter, and it seems from the many replies that a lot of people have opinions on this.

The ultimate linter would not only let you configure rules to your liking, but build your own rules if something were lacking. Luckily, ESLint supports exactly that!

So I decided to build an ESLint plugin to define an additional set of rules around => arrow functions: proper-arrows.

Before I explain anything about it, let me just point out: it's a set of rules that can be turned on or off, and configured, at your discretion. If you find even one detail of one rule helpful, it would be better to use the rule/plugin than not.

I'm fine with you having your own opinions on what makes => arrow functions proper. In fact, that's the whole point. If we all have different opinions on => arrow functions, we should have tooling support to let us pick and configure those different opinions.

The philosophy of this plugin is that, for each rule, when you turn the rule on, you get all of its reporting modes on by default. But you can of course either not turn the rule on, or turn the rule on and then configure its modes as you see fit. But I don't want you to have to go hunting for rules/modes to turn on, where their obscurity prevents them from even being considered. So everything comes on per rule.

The only exception here is that by default, all rules ignore trivial => arrow functions, like () => {}x => x, etc. If you want those to be checked, on a per-rule basis you have to turn on that checking with the { "trivial": true } option.

Proper Arrows Rules

So what rules are provided? Here's an excerpt from the project overview:

  • "params": controls definitions of => arrow function parameters, such as forbidding unused parameters, forbidding short/unsemantic parameter names, etc.
  • "name": requires => arrow functions to only be used in positions where they receive an inferred name (i.e., assigned to a variable or property, etc), to avoid the poor readbility/debuggability of anonymous function expressions.
  • "where": restricts where in program structure => arrow functions can be used: forbidding them in the top-level/global scope, object properties, export statements, etc.
  • "return": restricts the concise return value kind for => arrow functions, such as forbidding object literal concise returns (x => ({ x })), forbidding concise returns of conditional/ternary expressions (x => x ? y : z), etc.
  • "this": requires/disallows => arrow functions using a this reference, in the => arrow function itself or in a nested => arrow function. This rule can optionally forbid this-containing => arrow functions from the global scope.

Remember, each rule has various modes to configure, so none of this is all-or-nothing. Pick what works for you.

As an illustration of what the proper-arrows rules can check for, let's look at the "return" rule, specifically its "sequence" mode. This mode refers to the concise return expression of => arrow functions being a comma-separated sequence, like this:

var myfunc = (x,y) => ( x = 3, y = foo(x + 1), [x,y] );

Sequences are typically used in => arrow function concise returns to string together multiple (expression) statements, without needing to use a full { .. } delimited function body and an explicit return statement.

Some may love this style -- that's OK! -- but a lot of folks think it favors clever terse style coding over readability, and would prefer instead:

var fn2 = (x,y) => { x = 3; y = foo(x + 1); return [x,y]; };

Notice that it's still an => arrow function and it's not even that many more characters. But it's clearer that there are three separate statements in this function body.

Even better:

var fn2 = (x,y) => {
   x = 3;
   y = foo(x + 1);
   return [x,y];
};

To be clear, the proper-arrows rules don't enforce trivial styling differences like whitespace/indentation. There are other (built-in) rules if you want to enforce those requirements. proper-arrows focuses on what I consider to be more substantive aspects of => function definition.

Concise Summary

You and I almost certainly disagree on what makes good, proper => arrow function style. That's a good and healthy thing.

My goal here is two-fold:

  1. Convince you that opinions on this stuff vary and that's OK.
  2. Enable you to make and enforce your own opinions (or team consensus) with configurable tooling.

There's really nothing to be gained from arguing over opinion-based rules. Take the ones you like, forget the ones you don't.

I hope you take a look at proper-arrows and see if there's anything in there which you could use to ensure your => arrow functions are the best form they can be in your code base.

And if the plugin is missing some rules that would help define more proper arrows, please file an issue and we can discuss! It's entirely plausible we may add that rule/mode, even if I personally plan to keep it turned off!

I don't hate => arrow functions, and you shouldn't either. I just hate uninformed and undisciplined debate. Let's embrace smarter and more configurable tooling and move on to more important topics!

Kyle Simpson

About Kyle Simpson

Kyle Simpson is a web-oriented software engineer, widely acclaimed for his "You Don't Know JS" book series and nearly 1M hours viewed of his online courses. Kyle's superpower is asking better questions, who deeply believes in maximally using the minimally-necessary tools for any task. As a "human-centric technologist", he's passionate about bringing humans and technology together, evolving engineering organizations towards solving the right problems, in simpler ways. Kyle will always fight for the people behind the pixels.

Recent Features

  • By
    How to Create a RetroPie on Raspberry Pi – Graphical Guide

    Today we get to play amazing games on our super powered game consoles, PCs, VR headsets, and even mobile devices.  While I enjoy playing new games these days, I do long for the retro gaming systems I had when I was a kid: the original Nintendo...

  • By
    Vibration API

    Many of the new APIs provided to us by browser vendors are more targeted toward the mobile user than the desktop user.  One of those simple APIs the Vibration API.  The Vibration API allows developers to direct the device, using JavaScript, to vibrate in...

Incredible Demos

  • By
    Introducing LazyLoad 2.0

    While improvements in browsers means more cool APIs for us to play with, it also means we need to maintain existing code.  With Firefox 4's release came news that my MooTools LazyLoad plugin was not intercepting image loading -- the images were loading regardless of...

  • By
    Introducing MooTools HeatMap

    It's often interesting to think about where on a given element, whether it be the page, an image, or a static DIV, your users are clicking.  With that curiosity in mind, I've created HeatMap: a MooTools class that allows you to detect, load, save, and...

Discussion

  1. Interesting article. I like arrow functions a lot, but I can definitely see where you’re coming from. I would love to see more examples about where you file they are or are not readable.

    Regarding your last example, with the “sequence mode”, I think there is a more general style concern raised whenever multiple complex expressions or commands are combined in a single line. Specifically, it can be more or a headache to maintain and debug code written in this style.

  2. Max

    That was great!

    That function…

    var myfunc = (x,y) => ( x = 3, y = foo(x + 1), [x,y] );
    

    I didn’t even know you could write a function like that. I had to hit F12 to find out what it did. I’d be pissed if a team member wrote functions like that. That’s a good rule.

  3. Scott

    I thought it was just me…I was afraid to say something…thank you.

  4. Tommy Fritz

    Why not make an editor tool that doesn’t enforce how they should be written, but rather just displays them in the way the reader likes to read them? It seems like that would solve the majority of the issues here. Write them how you like, other people can read them how they like, everyone’s happy.

  5. Darren

    Very well written. Totally agree that being clear and explicit is much better than attempting to be cleverly concise.

  6. David Cameron

    Thank you for the article. I fully agree that readability should trump supposed cleverness disguised as concision in every case and I look forward to checking out your eslint plugin.
    I’m wondering though, how many of the instances of overly terse or indecipherable code you’ve described could be cleared up using a tool like Prettier.

  7. Arrow function are referred with no names when they are debugged, so no readability even in debugging.

    • Victor Longon

      Ohhh store than in a variable? :shrug:

  8. I particularly like your examples in this article. The article is spot-on!

  9. Guillaume

    There is also arrow functions as class properties when they really should not be arrow functions. This might be checked with an ESLint rule…

  10. I usually like your articles but it seem like you mostly argue about Arrow functions readability. Which is important but also highly personal.

    I might be missing something but wont the ‘name’ and ‘this’ rules will forbid using arrow functions as callbacks which is probably the main thing they are meant to do ?

  11. I think the issue with readability – for me at least – comes down to the natural way that we read: left to right, top to bottom.

    When we read code, we are often scanning top to bottom FIRST and then left to right.

    When your left most column is all var/const, it makes it really difficult to distinguish between different parts of the code since now you also need to scan left to right.

    I am really not a fan of this movement of JS towards arrow functions because I think it hurts readability. A whole generation hasn’t been exposed to McConnell’s Code Complete.

    “The information contained in a program is denser than the information contained in most books. Whereas you might read and understand a page of a book in a minute or two, most programmers can’t read and understand a naked program listing at anything close to that rate. A program should give more organizational clues than a book, not fewer.”

    “The smaller part of the job of programming is writing a program so that the computer can read it; the larger part is writing it so that other humans can read it.”

    The code is written once, but will be read dozens if not hundreds of times throughout its lifecycle. Arrow functions ultimately reduce readability for very minute gains at write time.

  12. MikeH

    I would say that the Structured Programming advocates like Kevlin Henney are trying to reify the programming communities in the modern era to not forget about the roots of procedural programming and how they are still 110% relevant in a modern era would agree with you.

    The Linting rules you have pretty much prove their point. I do think that it may be a bit premature to assign a function to a variable especially since it may only ever be needed the one time so inline is ok but you’d still have to have conversations with the coding team to agree to that. But that is just a matter of preference after all ;).

  13. tts

    It’s funny. The kind of childish responses you describe are so rife in our society right now at all levels. Just pick a topic and people react just like that.

    But as for the topic at hand…

    Given this?

    var fn2 = (x,y) => {
       x = 3;
       y = foo(x + 1);
       return [x,y];
    };
    

    Or this?

    function fn2(x,y) {
       x = 3;
       y = foo(x + 1);
       return [x,y];
    };
    

    If we’re talking readability, I prefer the latter. Why? Because it “looks” like a function has always looked.

    Sure, if you have a short one-liner or other very good reason. The arrow functions have their intended use. But otherwise, not.

    Simple Rules: Be explicit. Embrace white space. Don’t be clever.

    If it works, does the “one thing” it’s supposed to, you’ll likely not look at that code again for possibly years or ever. Nobody will. And when you do, it’s best to be painfully obvious.

    tl;dr : Always Prefer clarity over brevity. Don’t change your code, or your speech, to appease the attention deficient.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!