The invention of the function keyword

2023-01-26

Somewhere in a parallel universe close to yours... Steve Jobs stands on a large stage.

After the introduction of the iPhone 20 and the new iMac Air Max, the event is seemingly at an end. At the moment Steve is almost offstage, he turns around and says:

"One more thing!"

"Years ago when Apple acquired Netscape we tasked Brendan Eich to create JavaScript in 9 days. It was a tight deadline for sure, but he pulled through."

"Here at Apple we often wonder, just as the the rest of you, what we would have gotten if we had given Brendan 10 days instead."

Suddenly the light start to dim, the theater veiled in darkness.

The audience holds their collective breath... is it finally going happen after all these years...

"Also sprach Zarathustra" starts playing. Images appear in rapid succession: man on the moon, global warming solved, peace on earth.

Suddenly one word appears on the screen:

Just the one magical word.

The crowd roars, people fall into each others arms, emacs vs vim, spaces vs tabs, Tailwind vs CSS, nothing matters anymore.

function

Steve Jobs starts speaking with a sly smile:

"The tyranny of the const lambda is finally at an end."

Back to our universe.

My biggest pet peeve

This post is about something I can get very annoyed at: the const lambda otherwise known as the const fat arrow function.

The idea behind the story above is the following: if the function keyword was introduced today, we would never want use the const lambda anymore, and everybody would be busy writing blog posts about how wonderful the new function keyword is.

Just because some syntax is new, that does not make that syntax intrinsically better in every scenario!

Lets look at two examples of "const lambda":

// A mathematical calculation
const surfaceOfCircle = (diameter) => {
  const radius = diameter / 2;
  return radius * radius * Math.PI;
};

// A React component
const Greeter = ({name, age}) => {
  return <p>Hello {name}, you are {age} years old</p>
};

I find that the signal in the code above is not very strong. With signal I mean the programmers intent, in other words: what the code is going to do.

We read the code from left to right: first we read that a const variable is going to be created, but it can be anything at this point, then we read on and see the =>. Only now we know it is going to be a function.

Now take the previous examples, but then written with the function keyword:

// A mathematical calculation
function surfaceOfCircle(diameter) {
  const radius = diameter / 2;
  return radius * radius * Math.PI;
}

// A React component
function Greeter({name, age}) {
  return <p>Hello {name} you are {age} years old</p>
}

In my humble opinion I find the above example more readable. The signal / intent is stronger. From left to right my eyes rest on the function keyword, in an instant I know what is coming... a function.

I feel that this version requires less energy to read the code.

Now why do people use const lambdas when there is a perfectly fine function keyword?

I think there are two reasons: the first is that at some point it just became cool / hip to write functions this way. For no real good technical reason at all, it was just new and shiny.

The second reason: I think new JavaScript programmers learn "lambdas" solve the "this" problem, without actually understanding what the "this" problem is.

This then leads to the idea that lambdas are better than functions.

Understanding what problems lambda solves

Are there no good reasons to use lambda / fat arrow functions then? Not at all, they are ideal for callbacks. For example like in the various array methods:

const persons = [
  { name: "Maarten", age: 33 },
  { name: "Tosca", age: 31 },
  { name: "Owen", age: 5 },
  { name: "Jane", age: 2 },
];

const adults = persons
  .filter(person => person.age >= 18);

The nice thing about lambdas / anonymous functions it that they have no name. Naming things is hard. When using callbacks being forced to name the function just adds noise. In the example above the fact that you see the words adults and filter, is signal enough.

Lambda's have other one very nice property in that they do not manipulate the this. The following example was written in a time before the introduction of the lambda, the idea is that the birthday is executed after one second:

var person = { 
  name: "Maarten", 
  age: 33,
  birthday: function () {
    setTimeout(function() {
      console.log("Birthday timeout", this);

      this.age += 1;
    }, 1000);
  }
};
person.birthday();

The code above contains a subtle bug, being that the this in the timeout does not refer to the person but to the window. After one second the window.age is incremented by one, which results in NaN (Not a Number).

Now why is the this the window? This has to do with the unique way the this works in JavaScript. In JavaScript this is dynamically scoped and not lexically scoped. This means that in JavaScript the this is determined whenever the function is called.

In most other languages, such as Java, the this is statically scoped, meaning the this is always the same, and you figure out what the this is based on where you read it.

In JavaScript a this cannot be divined purely on location alone. You have to know how the function is called. You can quite easily "change" the this by taking functions out of objects for example:

window.age = 41;

const person = { 
  name: "Maarten", 
  age: 33,
  birthday() {
    // You think this this always points to person.
    this.age += 1
  }
};

// But now we extract the birthday unexpectedly
const birthday = person.birthday;

// window.age is now 42!
birthday();

Bottom line: never make assumptions about this!

If you want to read up about this in detail I recommend reading this page from on MDN.

My personal cheatsheet for figuring out the this:

  • The this lies before the ., for example in person.birthday() the this is person.
  • The this lies before the =, for example in const jane = new Person() the this would be jane inside the constructor of Person.
  • If there is no . and no = the this points to the window. For example in birthday() the window is the this.
  • Remember: when in doubt it is useful to console.log about!

Now back to the birthday this problem, in pre-lambda we would solve this problem like so:

var person = { 
  name: "Maarten", 
  age: 33,
  birthday: function () {
    // Securing the "this" in a variable
    // so it can be used in the timeout.
    var self = this;

    setTimeout(function() {
      console.log("Birthday timeout", this, self);

      // Here we use self instead of this because
      // this would point to the window otherwise.
      self.age += 1;
    }, 1000);
  }
};

// After one second the age will increase
person.birthday();

With lambda / fat arrow functions we do not need this old trick anymore. Lambda functions preserve the this:

const person = { 
  name: "Maarten", 
  age: 33,
  birthday() {
    setTimeout(() => {
      console.log("Birthday timeout", this);

      this.age += 1;
    }, 1000);
  }
};
person.birthday();

Lambdas / arrow functions whatever you want to call them are in fact quite useful.

My advice

Use the function keyword to define named functions, and steer clear of using const lambda's for this. This gives your reader a better signal, and requires less mental energy to read.

Do use lambda's for callbacks, this prevents you from having to name them, which is hard, and it will also make your code more compact as a bonus. The most important reason to use them for callbacks is preventing nasty this bugs.

This post originally appeared in dutch on the fronteers website as part of the 2022 advent calendar.

Please share this post if you enjoyed it.