Sometimes, you want to create new types with a constructor-like interface. Sometimes, inheritance doesn’t capture what you really want to do. In these cases, typical object-oriented programming languages get in your way.
A first example.
Let’s say you want to model an HTTP response object. Forgive my rusty Java. I would argue that the following represents the most natural way of expressing the object.
class HTTPResponse {
int responseCode;
Map<String, String> headers;
String entityBody;
...
}
Now, you realize that an HTTP 201 (“Created”) response always has a Location header. You want to provide some programmatic support for this. There are many ways to model this, but let’s walk through the most intuitive thought process.
A Created response is a HTTP Response. This encourages us to use inheritance.
class HTTPCreatedResponse extends HTTPResponse {
...
}
All HTTPCreatedResponses have a responseCode of 201. This is a property of the HTTPCreatedResponse *class*.
class HTTPCreatedResponse extends HTTPResponse {
protected static final int responseCode = 201;
}
But, but, but. responseCode is an instance variable of HTTPResponse. We don’t want to specify it in two places. Our intuitions have led us into a corner.
Note that I am not saying “YOU CANNOT MODEL THIS IN JAVA.” Instead I make the gentler claim that one cannot model this pattern intuitively.
The problem of slippery subtypes is common. Sometimes it manifests itself as a desire for polymorphic class variables:
class HTTPResponse {
static int responseCode;
int getResponseCode() { return responseCode; }
int setResponseCode(responseCode) { this.responseCode = responseCode; }
...
public HTTPResponse(responseCode, ...) { ... }
}
class HTTPOKResponse extends HTTPResponse {
static final int responseCode = 200;
...
}
class HTTPCreatedResponse extendsHTTPResponse {
static final int responseCode = 201;
String location;
public HTTPCreatedResponse(location, ...) { this.location = location; ... }
}
...
HTTPOKResponse(...).getResponseCode() /* 200 */
HTTPCreatedResponse(...).getResponseCode() /* 201 */
Consider modeling symbols in a parser. It is common to see code like:
STATEMENT_TERMINATOR = new Symbol(";");
BLOCK_OPEN = new Symbol("{");
BLOCK_CLOSE = new Symbol("}");
Having done this, we might wish to later instantiate these symbols when we encounter them, as if new STATEMENT_TERMINATOR(line, column) were possible. But it is not. It’s not quite inheritance. We don’t want actually care too much about subtype substitutability. A functional programmer might think of this as a desire to curry types, in a way. A programmer in a prototype-inheritance language would just chuckle condescendingly and be on her way.
January 16, 2009 at 3:00 pm
Hrm.. I know the frustration here. But maybe it’s intuition that’s slippery though. Not being in the javascript headspace I’d probably have gone right along without trouble and initialised responseCode in a subclass constructor, and made an instance of a symbol factory.
Aren’t there things a Java-head would find counter-intuitive in Javascript?
January 17, 2009 at 12:07 am
[...] « Slippery Types [...]