I have been working recently on Piratenoth, a release of Trosnoth which defaults to using a pirate theme. For those of you who haven’t yet clicked on the Trosnoth link above, Trosnoth is a fun 2D team game. We’ve been updating the game so that rather than being hard-coded, things like game colours and graphics are different depending on what theme the user has selected.
This has been a fun exercise in refactoring, but it highlighted for me something which has always annoyed me about the Trosnoth code: attribute chains. For example:
Some (but not all) of these attribute chains come about due to the tree structure of the objects in the game: the player interface is the child of the game interface which is the child of the details interface and so on. And it got me thinking of a creative new not-quite-OO way of doing things.
Imagine a programming language where every function is executed in some context, where the context is represented by some object. The context is accessible within the function by some name like this, self or context. The context object is also allowed to have a parent object and a name associated with that parent object.
Now here comes the fun bit: within a function, you can access any ancestor of the context object, using the name of that ancestor. If there are more than one ancestor with the same name, the most recent ancestor would be returned.
So let’s apply that to the example from Trosnoth above. Suppose we have the following context objects:
- app: represents the application
- interface: child of app
- detailsInterface: child of interface
- gameInterface: child of detailsInterface
- playerInterface: child of gameInterface
Now if a function is executed in the context of the player interface, it would be able to just say:
The name “detailsInterface” would be searched for up the ancestor chain, and we’d eliminate a whole bunch of the chained attributes. And it would eliminate the need to pass the application object to every constructor (as we currently do) because it can be accessed implicitly through the ancestor chain.
There would need to be some way of selecting the context in which to run a function. For example:
And there would be other considerations too. The designer of such a language would have to decide whether ancestor names should have precedence over global variables or vice versa. And it might lead to a different programming approach which could be interesting.
I don’t plan to actually use this approach in Trosnoth. We already have a different approach for avoiding the ugliness of attribute chains, and we’re slowly updating our code to avoid such ugliness. But I may have a bit more of a play with this idea in the weeks to come. And it gives me all sorts of other related ideas about programming language design. So much fun to be had!