teaching machines

CS 330: Lecture 33 – Closures Continued

April 27, 2018 by . Filed under cs330, lectures, spring 2018.

Dear students,

Last time we ended in the middle of a discussion about the dangers of closures oversharing values. We fixed the situation by adding a function to introduce a new scope for each closure so they wouldn’t clobber each others’ state. This situation happens frequently enough in Javascript that there’s a slightly simpler idiom called an immediately invoked function expression (IIFE). It looks kind of gross at first, but it eliminates the need to bind the function to a name:

for (var i = 0; i < buttons.length; ++i) {
  (function(iUnshared) {
    buttons[iUnshared].onclick = function() {
      alert(iUnshared);
    }
  })(i);
}

iUnshared only exists in the scope of the function, so we will have no unintended sharing across buttons.

We could also have fixed this particular example by not using a for loop in the first place. A forEach HOF would also introduce a new scope for the index variable:

buttons.forEach((button, i) => alert(i));

But we’re bound to run into oversharing situations sooner or later, and it’s important that we can read and write IIFEs.

This automatic capturing of the surrounding environment in a closure is often a wonderful thing. In fact, I’d go so far as to say that lambdas need closure semantics to be useful. Suppose for a moment that lambdas could not access variables from the surrounding scope. Consider writing a compound slider/spinner widget in HTML:

<input id="slider" type="range">
<input id="ticker" type="number">

When you change one of the widgets, you want the other widget to change to stay in sync, so you write this code:

var slider = document.getElementById('slider');

slider.oninput = function() {
  // ?
}

How do you update the ticker? In the absence of closures, the only information we have is what’s given to our lambda through parameters. Only the information known and deemed worthy by the writer of the event system will ever get sent to our callbacks. In this case, we do get some information sent to our callback:

slider.oninput = function(event) {
  console.log(event);
}

But it’s not enough to update the ticker. Instead, we do have closure semantics and we can access variables from the surrounding scope:

var slider = document.getElementById('slider');
var ticker = document.getElementById('ticker');

slider.oninput = function(event) {
  ticker.value = slider.value; 
}

And vice versa:

ticker.oninput = function(event) {
  slider.value = ticker.value; 
}

So, a closure is a chunk of unevaluated code packaged up with its state. Isn’t that a crazy idea? Have you ever seen anything else like this—this marriage between code and data?

You have. Objects. In fact, if you ever find yourself in a language that lacks one of them, you can use the other to implement the missing feature. Javascript, for instance, doesn’t have objects in the same sense that Java does. But since it supports closures, we can add them.

In general, an object is a set of methods that share instance variables. To implement this in Javascript, we need a set of closures that share state. No one else should be able to access that state, so we’ll need a function to introduce a new scope. That function will serve as the “constructor”:

function Hero(name, hp, mp) {
  // ...
}

Each Hero should be a collection of closures. We’ll just stick to functions for curing the Hero. We’ll put all our closures in a dictionary:

function Hero(name, hp, mp) {
  var object = {};

  object.cure = function() {
    hp += 10;
    mp -= 5;
  }

  object.cura = function() {
    hp += 30;
    mp -= 10;
  }

  object.curaga = function() {
    hp += 100;
    mp -= 30;
  }

  return object;
}

First, note that this function isn’t really a constructor in the traditional sense. It might as well have been named createHero. Second, notice that all the lambdas are accessing the same shared state. I didn’t need to assign instance variables. The closure semantics automatically “assign” them. Third, we don’t really have an object here. We just have a dictionary.

Let’s add another function to help us test our object:

object.log = function() {
  console.log(name + ' has ' + hp + ' HP and ' + mp + ' MP.');
}

We can create our object by invoking the instructor and then call methods on it:

var zorn = Hero();
zorn.log();
zorn.cure();
zorn.log();
zorn.cura();
zorn.log();
zorn.curaga();
zorn.log();

And there we have objects in Javascript! All we’re really doing here, though, is making function calls. There’s no this struct being passed around. The functions themselves have the data in their closure. zorn itself does not nothing but hold the functions.

There are some better ways for emulating objects in Javascript, including ways that provide a form of inheritance. I encourage you to read up prototypal inheritance if you are interested.

Now let’s consider closures in Java. Let’s print out a numbered list of planets using a lambda:

int i = 1;
Library.foreach(planets, p -> {
  System.out.printf("%d. %s%n", i, p.getName());
  i += 1;
});

But this fails to compile because Java doesn’t truly support closures. Inner classes and lambdas do not truly share bindings with outer scopes. Rather, the inner construct gets an independent copy of the outer scope’s binding. And to emphasize that the two bindings are separate, that a change in one will not result in a change in the other, the compiler says that you can’t change them at all—either of them. They must be final:

final int i = 1;
Library.foreach(planets, p -> {
  System.out.printf("%d. %s%n", i, p.getName());
  // i += 1; 
});

Sadly, final variables can’t be changed. So much for our numbered list! We have a few options:

Let’s investigate the last of these. Consider this attempt at firing off 10 threads:

public static void main(String[] args) {
  for (int i = 0; i < 10; ++i) {
    new Thread(() -> System.out.println(i)).start();
  }
}  

First, it is reasonable for a thread to know its serial number. For example, thread i might process task i or chunk i of some large dataset. Second, this code doesn’t compile. Variable i is not final, and therefore by the rules of Java cannot be closed over. But the loop body presents us an opportunity for a new scope:

for (int i = 0; i < 10; ++i) {
  int iThread = i;
  new Thread(() -> System.out.println(iThread)).start();
}

A local variable is enough to appease the compiler. There’s no oversharing between threads.

In summary, lambdas promise brevity and simplicity for working with higher-order functions, but different languages approach sharing and closures differently. Haskell, incidentally, avoids all the concerns of oversharing because variables are immutable.

Here’s your TODO list:

See you next time!

Sincerely,

P.S. It’s time for a haiku!

Data married code
They’d been an item object
Now there’s some closure

P.P.S. Here’s the code we wrote together:

buttons.html

<!DOCTYPE html>
<html>
<head>
  <title>...</title>
</head>
<body>
  <input id="button0" type="button" value="Click A!"> 
  <input id="button1" type="button" value="Click B!"> 
  <input id="button2" type="button" value="Click C!"> 

  <script>
//var j;

var buttons = [
  document.getElementById('button0'),
  document.getElementById('button1'),
  document.getElementById('button2'),
];

/*
for (var i = 0; i < buttons.length; ++i) {
  //j = i;
  (function(iButton) {
    buttons[iButton].onclick = function() {
      alert(iButton);
    };
  })(i);
}
*/

Array.prototype.forAndy = function(whatdo) {
  for (var i = 0; i < this.length; ++i) {
    whatdo(this[i], i); 
  }
}

//buttons.forAndy(function(button, i) {
//  button.onclick = () => alert(i);
//});

buttons.forAndy((button, i) => {
  button.onclick = () => alert(i);
});

  </script>
</body>
</html>

slick.html

<!DOCTYPE html>
<html>
<head>
  <title>...</title>
</head>
<body>
  <input type="range" id="slider">
  <input type="number" id="ticker">

  <script>
var slider = document.getElementById('slider');
var ticker = document.getElementById('ticker');

slider.oninput = function(e) {
  ticker.value = slider.value;
}

ticker.oninput = function(e) {
  slider.value = ticker.value;
}
  </script>
</body>
</html>

hero.js

function Hero(name, hp, mp, clazz, race) {
  var object = {};

  // object = {
    // cure: function() {
      // ...
    // },

    // cura: function() {
      // ...
    // }
  // }

  object.cure = function() {
    hp += 10;
    mp -= 7;
  }

  object.cura = function() {
    hp += 50;
    mp -= 27;
  }

  object.curaga = function() {
    hp += 100;
    mp -= 47;
  }

  object.log = function() {
    console.log(name + " has " + hp + " HP, " + mp + " MP.");
  }

  return object;
}

var zorn = Hero('Zorn', 7000, 842, 'Computer Scientist', 'Elf');
zorn.log();
zorn.cure();
zorn.log();

Planet.java

public class Planet {
  private String name;
  private double ausFromTheSun;
  private double relativeMass;

  public Planet(String name,
                double ausFromTheSun,
                double relativeMass) {
    this.name = name;
    this.ausFromTheSun = ausFromTheSun;
    this.relativeMass = relativeMass;
  }

  public String getName() {
    return name;
  }

  public double getAUs() {
    return ausFromTheSun;
  }

  public double getMass() {
    return relativeMass;
  }

  public String toString() {
    return String.format("%15s %6.2f %6.2f%n", name, ausFromTheSun, relativeMass);
  }
}

Whatdo.java

public interface Whatdo<A> {
  void doer(A item);
}

ForeachTest.java

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.List;

public class ForeachTest {
  public static <A> void foreach(List<A> list, Whatdo<A> action) {
    for (A item : list) {
      action.doer(item);
    }
  }

  public static void main(String[] args) {

    for (int i = 0; i < 10; ++i) {
      int j = i;
      new Thread(() -> System.out.println(j)).start();
    }

    /* List<Planet> planets = Arrays.asList( */
       /* new Planet("Saturn", 9.5, 95.0), */
       /* new Planet("Earth", 1.0, 1.0), */
       /* new Planet("Venus", 0.7, 0.815), */
       /* new Planet("Jupiter", 5.2, 318.0), */
       /* new Planet("Uranus", 19.6, 14.0), */
       /* new Planet("Neptune", 30.0, 17.0), */
       /* new Planet("Mars", 1.5, 0.107), */
       /* new Planet("Mercury", 0.4, 0.055) */
      /* );  */

    /* final int number = 0; */
    /* foreach(planets, planet -> { */
      /* System.out.print(number + ". " + planet); */
      /* ++number; */
    /* }); */
  }
}