A type system is the most cost effective unit test you’ll ever have.
Peter’s a man who knows his type systems – good, bad and ugly.
I saw a nice example of using the type system to prevent bugs when I worked on Pixel Bender at Adobe. Pixel Bender is an image processing toolkit. Images are made up of pixels, however, pixels are not always 1×1 in size, and they are not always square – in particular, video processing often uses rectangular pixels. At the time we started the project we hadn’t come to grips with non-square, non-unit-sized pixels so we started with a single coordinate system where moving right one unit moved you one pixel across, and moving up one unit moved you one pixel up. This pixel coordinate system is not isotropic – one unit on the X axis is not necessarily the same distance as one unit on the Y axis. We wanted to be able to represent rectangular areas of pixels so we created two classes – Position (containing x and y values) and Extent (containing width and height values).
We got away with our pixel coordinate system for quite a long time, however eventually we realized that we needed to have an underlying, isotropic, coordinate system. We called this world coordinates. We converted between pixel coordinates and world coordinates by using the width and height of a single pixel. Since we already had Position and Extent classes we used them for our world coordinate system as well.
Imagine you see a function that takes a Position as an input parameter. Is that Position in world coordinates or pixel coordinates? We instituted some naming conventions. They sort of worked. Mostly. Except when they didn’t. We had plenty of tests, which caught some of the bugs, but working with a Position or an Extent was more fragile than we wanted.
Our solution was to create separate classes for pixel and world coordinates. We ended up with:
The two Position classes were identical apart from their name, as were the two Extent classes. In both cases we just had the simplest possible wrapper around two double values with appropriately named accessor methods. We could have used some template magic to avoid code duplication. We could have used some “clever” macros (and I put “clever” in quotes to indicate my disdain). We actually just wrote separate classes – very simple classes, with some repetition of boilerplate between them. Breaking the normal “don’t repeat yourself” rule seemed justified here. For a little (and it really was a little) more work up front we had a very simple system that served us well. If we had been working with tens or hundreds of different coordinate systems we might have used another method to generate the code, but we only had two, and we were relatively convinced that we were never going to add more than one or two additional coordinate systems (as it turned out we only needed pixel and world coordinates).
By looking at code you knew what coordinate system was in use. When writing new code you could specify what coordinate system it needed its inputs to be in. If you tried to pass a value in the wrong coordinate system to a function the compiler would stop you. The system worked. We made it easier to do it right than to do it wrong – using the type system eliminated a whole class of bugs from our code.