Play with closures

Here are two snippets of JavaScript code from class. Here is a Javascript playground you can use to run this code. (Note the Maximize button at the top, which makes it possible to see more of the code at once.)

Please play around with these examples — make changes, try experiments, see what works, see what breaks — until you get to a point where they’re starting to make sense. Post questions in the class Slack channel!

You might also want to skim over the Graphing Calculator activity from COMP 127, just to jog your memory. Java and Javascript have very similar syntax for anonymous functions / closures / lambdas. (“Lambda” or “lambda function” are just alternate words for “closure.”)

// `console.log` is a function, and a function is a value!
// That means we can assign that value to a new variable,
// making a nice alias for an existing function:
let print = console.log;

let x = 0;

function sayHi() {
  print("hi number " + x + "!");
  x++;
}

function thrice(f) {
  f();
  f();
  f();
}

thrice(sayHi);

Check your understanding: why doesn’t this work??

thrice(sayHi());
let print = console.log;

function thrice(f) {
  f();
  f();
  f();
}

function makeCounter(msg) {
  let count = 0;
  return () => {
    print(msg + " is now " + count);
    count++;
  };
}

let penguinCounter = makeCounter("penguin");
let duckCounter = makeCounter("duck");
let lionCounter = makeCounter("lion");

thrice(penguinCounter)
duckCounter()
penguinCounter()
thrice(lionCounter)
thrice(duckCounter)

Things to note about that example:

  • The return value of makeCounter is an anonymous function. That’s what the (…) => { … } syntax means: “anonymous function.” That entire function is a single value! We can return it, pass it around, assign it to a variable, and put it in a larger expression just like any other value in the language.
  • count is a local variable. Normally its lifetime would end with the closing brace of makeCounter. However, the anonymous function captures the count variable, which extends its lifetime! The returned function will continue to reference that count variable even after makeCounter returns.
  • Because count is a local variable of makeCounter, we create a new count variable every time we call makeCounter. That is why penguinCounter, duckCounter, and lionCounter all count independently: each one captured a different instance of the count variable from a different call to makeCounter. In this way, creating a closure with captures is a lot like creating an object with instance variables!

I didn’t point it out in class, but Javascript’s function syntax is just syntactic sugar. We could have also defined all of these functions by using the anonymous function literal syntax (…) => { … } and then assigning them to variables, like this:

let print = console.log;

let thrice = (f) => {
  f();
  f();
  f();
}

let makeCounter = (msg) => {
  let count = 0;
  return () => {
    print(msg + " is now " + count);
    count++;
  };
}

let penguinCounter = makeCounter("penguin");
let duckCounter = makeCounter("duck");
let lionCounter = makeCounter("lion");

thrice(penguinCounter)
duckCounter()
penguinCounter()
thrice(lionCounter)
thrice(duckCounter)