02.14.10

All programs should be compilers

Posted in General development at 9:26 am by Kyoryu

Been reading Steve Yegge’s stuff.  He’s smart.  You want him working for you.

This post reminded me of Rob’s Grand Theory of Good Object Oriented Programming (I’m the Rob in question, BTW).

If you’re asking what the hell compilers have to do with object oriented programming, read on.

Fundamentally, what a compiler does is transform data.  Each phase of the compiler makes a different transformation of the data.  Most steps will spit out the data in an entirely different form, which is neat because it means that any given phase generally doesn’t have to know about anything except for the data format it gets, and the format it spits out.  Phases are usually one-way.

A simple compiler might take in a text file, and transform it into a token stream (lexing phase).  Then, it will take the token stream and turn that into an intermediate representation such as an AST (parsing phase).  After that things get fun.  Usually you’ll then mess with the AST a few times, simplifying it, and then transforming it for perf reasons.  This may or may not be the same tree format.  Once you’re done with that, you’ll start the process of transforming it into something resembling the native format of the machine you’re actually compiling it to.

And so on, and so forth.

What we have here is a series of things that take data in, mangle it, and spit it out the other side, often in a different format.

So, again, what the hell does this have to do with object-oriented programming?

Well, it turns out you can model just about any damn thing you want using this programming model.  For instance, say you want to write a calculator program.  You might be tempted to write that by checking to see if a button was pressed, if so adding a value to an expression tree, and then calling calculate on the expression tree to get a value, and then setting the text on your output.  This could all be done as a single large method, but you’d probably realistically break it into multiple functions if not objects (though the actual code path would likely be roughly equivalent).

You could do that.  Or, you could treat it like a compiler.

In our calculator-compiler, we’d start with the input being the coordinate of a mouse click.  We would transform that into a symbol, much like a token in a lexer.

This token would then be passed into an expression builder (much like a parser).  As a result of this, our parser would then output a current value, which could either be a number, an error condition, or perhaps some other kind of message.

The next piece in line would take the output message and transform it into some kind of data structure representing formatted text (possibly including colors and whatnot).

And the final piece would take that formatted data structure and turn it into a bitmap to be displayed in our output area.

Now, I’d write the calculator program in such a way that each of the pieces I just described was a separate class (at least.. maybe more than one!)  That’s a lot of classes.  Why would I do this?

Well, it turns out that doing things like that has a lot of benefits.  First, it means that any of your code, except for the parts at either the very beginning of the chain (where you read the mouse input) or the very end (where you actually display the bitmap) can be tested in isolation.   That’s pretty cool.

Secondly, it means that except for data, no part of the system really knows anything about any other part of the system.  That makes changing things much, much easier.

Third, the system becomes almost entirely stateless.  And what state exists is completely localized to a single class.  State is only used to modify the outputs of a given class, not to be queried by other classses.

Fourth, locking becomes really easy.  A given class locks when it is modifying its local, internal state, and releases the lock before it calls the next object.

Fifth, multithreading becomes easy.  If this is all modeled as objects making void calls on each other, then you can make calls to objects on pretty much arbitrary threads.With no return value, and no real assumption of state modification, it gets easy.

Sixth, your error scope becomes smaller.  Since any given class is only responsible for a specific, defined data transformation, it’s really irrelevant what the next guy in the chain does.  It’s not really your responsibility.

Okay, but does this work for interactive apps?  Or apps with complex logic, or multiple inputs or outputs?

Yep!  It works fantastic!  It just means that your chain isn’t linear, that you have some areas where multiple objects talk to one, or where one object outputs data to many.  Hell, it even works in cases where objects can cause chains that result in calls back to themselves.

It doesn’t preclude more “typical” OO programming, either.  In fact, I often use more typical techniques when creating and describing data structures, rather than program flow.

One of the other interesting things that happens in this style of programming is that you often realize that what you’re doing is very analogous to creating an AST of your own.  Which means that you can, conceivably, create a generic form of whatever it is you have that wires your objects together that works on some input.  Some input that might be a tree-like structure, which could be generated from a token stream…

See where this is going?

There’s other advantages, too.  “Normal” OO programming often runs into a number of common problems.  First, you’ve got the efficiency problem.  Calling “User.Save()” will perhaps make a call to a database, and looping over 20 Users will result in 20 calls.  That sucks.  Looking at the problem from this approach, you’d transform that array of 20 pieces of data into a SQL query, which would then be passed to something which could hand it to the SQL server itself.

Another common problem in typical OO systems is increased complexity due to either over-generalization, or overuse of inheritance.  This problem typically manifests itself as code that jumps across  mutliple different classes through a maze of calls that is impossible to trace.  With the ability to draw strong demarcations of responsibility at object boundaries, the code inside a given object tends to be very specific.  Since a single object isn’t trying to manage the entire data flow of a single piece of data, you end up avoiding a lot of coupling problems.

By the way, I don’t think I’m really alone in this.  If you look at the GoF book from this perspective, a whole lot of the patterns make more sense than if you look at them from a more typical viewpoint.  Also, what I’ve just described is, in many ways, the Actor model that is starting to become somewhat in vogue.

It’s also works exceptionally well with test-driven development, given that 95% of your code ends up stateless, and not interacting with the system at all.

Leave a Comment

You must be logged in to post a comment.