Color-flavor locking breaks chiral symmetry

(Attention conservation notice: Post not actually about quantum chromodynamics.)

If you listen to the Q&A for Josh Bloch’s Closures Controversy talk at JavaPolis, you’ll hear me ask a silly question around minute six — namely, whether Josh should see the BGGA closures proposal as a benefit rather than a hazard, on account of the material they’ll provide for the Java Puzzlers he and Neal Gafter are so fond of. Well, it looks like Neal’s getting a head start with “Closures Puzzler: Neapolitan Ice Cream.”

This looks like a good opportunity to stretch my brain, and also try out the fancy syntax highlighter that Rahel discovered. Let’s see how it goes.

Here’s the puzzler. What does it print?

enum Color {
  BROWN(Flavor.CHOCOLATE),
  RED(Flavor.STRAWBERRY),
  WHITE(Flavor.VANILLA);

  final Flavor flavor;

  Color(Flavor flavor) {
    this.flavor = flavor;
  }
}

enum Flavor {
  CHOCOLATE(Color.BROWN),
  STRAWBERRY(Color.RED),
  VANILLA(Color.WHITE);

  final Color color;

  Flavor(Color color) {
    this.color = color;
  }
}

class Neapolitan {

  static  List<U> map(List list, {T=>U} transform) {
    List<U> result = new ArrayList<U>(list.size());
    for (T t : list) {
      result.add(transform.invoke(t));
    }
    return result;
  }

  public static void main(String[] args) {
    List colors = map(Arrays.asList(Flavor.values()), {
      Flavor f => f.color
    });
    System.out.println(colors.equals(Arrays.asList(Color.values())));

    List flavors = map(Arrays.asList(Color.values()), {
      Color c => c.flavor
    });
    System.out.println(flavors.equals(Arrays.asList(Flavor.values())));
  }
}

Now, right away we can see there’s something suspicious going on here with the order of initialization — Color requires Flavor and Flavor requires Color; who wins? This is the sort of question I probably should be able to answer, but in practice can never remember until I see a symptomatic bug. So let’s find a symptomatic bug:

for (Color c: Color.values()) {
  System.out.println(c + ": " + c.flavor);
}

for (Flavor f: Flavor.values()) {
  System.out.println(f + ": " + f.color);
}

Result:

BROWN: CHOCOLATE
RED: STRAWBERRY
WHITE: VANILLA
CHOCOLATE: null
STRAWBERRY: null
VANILLA: null

Say what now?

But if you think about it, it makes sense. Color is declared first, but because Color requires Flavor, Flavor is actually initialized first — so when you get to, say, CHOCOLATE(Color.BROWN), well, Color hasn’t been (can’t have been) fully initialized, so Color.BROWN is null.

At this point I’m tempted to just say my answer is:

false
true

…but let’s make sure there isn’t more to it than that. So far I’ve been too lazy to download and install the BGGA closures prototype, so let’s see how hard it is to fake this in straight Java:

class Neapolitan {

  static interface Transform {
    U invoke(T t);
  }

  static  List<U> map(List list, Transform transform) {
     List<U> result = new ArrayList<U>(list.size());
     for (T t : list) {
       result.add(transform.invoke(t));
     }
     return result;
  }

  public static void main(String[] args) {
    List colors = map(Arrays.asList(Flavor.values()),
      new Transform() {
        public Color invoke(Flavor t) {
          return t.color;
        }
      });
    System.out.println(colors.equals(Arrays.asList(Color.values())));

    List flavors = map(Arrays.asList(Color.values()),
      new Transform() {
        public Flavor invoke(Color t) {
          return t.flavor;
        }
      });
    System.out.println(flavors.equals(Arrays.asList(Flavor.values())));
  }
}

Well, that didn’t seem so hard — what do we get?

true
false

Say really what now?

Let’s put a print statement in that map() method and see if we can figure out what’s going on.

  static  List<U> map(List list, Transform transform) {
     List<U> result = new ArrayList<U>(list.size());
     for (T t : list) {
       U u = transform.invoke(t);
       System.out.println(t + " => " + u);
       result.add(u);
     }
     return result;
  }

. . .

CHOCOLATE => BROWN
STRAWBERRY => RED
VANILLA => WHITE
true
BROWN => null
RED => null
WHITE => null
false

Holy late binding, Batman! The order of initialization’s reversed!

And I think I know why. To demonstrate, let’s take those original print loops and reverse them:

for (Flavor f: Flavor.values()) {
  System.out.println(f + ": " + f.color);
}

for (Color c: Color.values()) {
  System.out.println(c + ": " + c.flavor);
}

. . .

CHOCOLATE: BROWN
STRAWBERRY: RED
VANILLA: WHITE
BROWN: null
RED: null
WHITE: null

And behold! we get the same effect:

Why? Because classes are loaded at runtime. I said up there that Color required Flavor, and I was right. But whether Flavor or Color is loaded first has nothing to do with declaration order, and everything to do with which one happens to get called first in the code. Call Color first (as in the first set of print loops), and Flavor gets incompletely initialized; call Flavor first (as in the second set of print loops, and in the puzzler itself), and it’s Color that suffers.

(Note that the List<Color> reference doesn’t count — generics are erased at run-time, so at run-time when the JVM gets to the declaration of colors, it’s just a plain old untyped List. The Flavor.values() call is the first time one of these classes actually gets loaded.)

The moral of the story? If you ask me, it’s — as with so many of these — don’t code like this. 🙂 But also: just because that enum variable looks vaguely constant-ish doesn’t mean the compiler’s going to inline it for you, and if the compiler’s not going to inline it for you, you have to think about how the code behaves at run-time.

Mostly, though, just don’t code like this.

(Now, I just hope, in the name of the Knights of the Lambda Calculus, that bringing in BGGA closures doesn’t change the answer…)

(Update: Neal’s posted some solutions, encapsulating flavor and color and deferring the resolution till run-time; worth a look.)

Comments closed due to spam.