Monday, November 24, 2014

Object immutability and operational definitions of games

One of my students made a fascinating connection today between game design and software design. He happens to be in both CS222 (Advanced Programming) and my honors colloquium on game design.

In CS222, we talked about mutability vs. immutability in object-oriented design. I used the example of a two-dimensional Vector class, and I started by asking the students to implement vector addition. After a few minutes (and a visual reminder of what vector addition means),  I showed an implementation very like theirs, using mutable objects.

class Vector {
  private float x;
  private float y;
  public Vector(float x, float y) {
    this.x=x;
    this.y=y;
  }
  public void add(Vector v) {
    this.x+=v.x;
    this.y+=v.y;
  }
}

The students agreed that this works, but then I pointed out that this is not actually modeling a vector at all, but rather, a box that holds two numbers. Vectors don't change under addition. We talked about how a scalar is a one-dimensional vector, and when you add 3 and 5, you change neither 3 nor 5. (I also drew some object diagrams to demonstrating how aliasing is a problem for mutable objects. Unexpectedly, many of the students fell into the trap of predicting the wrong output for a simple aliasing problem, but this means that it was time well spent.)

One of the students figured out that the add method should actually be returning a new vector, and this led into my showing the immutable implementation:

class Vector {
  private float x;
  private float y;
  public Vector(float x, float y) {
    this.x=x;
    this.y=y;
  }
  public Vector add(Vector v) {
    return new Vector(this.x+v.x, this.y=v.y);
  }
}

The students seemed to recognize that this really was a vector because it was behaving like one: adding a vector to another does not change either. We re-drew some of the object diagrams and saw how aliasing still happened but was no longer a problem.

With just a few minutes left in class, I pointed out that this example is relatively simple because everyone agrees on what a vector is in two-dimensional space. I wanted to demonstrate how this technique applies to other domains, so I pointed out that one of the groups was making a simplified course planner for their nine-week project. Although they probably look at a course only as it is offered, I pointed out that we faculty change properties of courses regularly: we change the titles, descriptions, credit hours, and more. For example, several years ago we changed our CS1 course from three credit hours to four. I posed this question: should the course object be mutable or immutable? Should we model this as changing a course, or as creating a new course that has the new properties? I think students recognized that this is a design decision with no definitive right or wrong answer, but that the decision would have a ripple effect throughout a system.

One of the students approached me after class and said that he believed courses should be immutable—that the 3-credit version of CS1 is a different course from the 4-credit one. I tend to agree, and I probably said or at least implied as much in my presentation. He told me that he realized a parallel between this problem and the problem of defining a game: that a game is defined by its mechanics. In my game design colloquium, I had mentioned the provocative idea that there is a one-to-one correspondence between mechanics and games: that is, that changing a rule doesn't change the game, but rather creates a new one. For example, choosing to put money into Free Parking in Monopoly doesn't change Monopoly, it means you're playing a different game with your Monopoly set. (To be clear, the Monopoly rules do not involve putting money into Free Parking; it's just a common house rule.)

I had not previously thought about the connection between operational definitions of games and a preference for immutable objects in object-oriented design. There's a sense in which this perspective on games is itself object-oriented: the game is defined by the features it supports. It's not what we call the game that matters, but how it operates. However, names are important for thinking and communicating about designs. We can call the first vector implementation above "Vector" even though it doesn't behave like one does in mathematics, but doing so leads to cognitive dissonance since vectors don't change under addition any more than integers do. We can call the immutable implementation "Vector" without hedging or hesitation. If you play Monopoly by its rules, there should be no ambiguity in calling it "Monopoly," but if you sit with friends to play a game where you put money into Free Parking, you should probably call it "Monopoly with the Free Parking variant" or something similar. Otherwise you will have a similar conflict when your naming does not match the behavior of the named system.

Also, if you're playing Monopoly, I have some other games to recommend to you, but that's a different story.

No comments:

Post a Comment