| Home > Technical articles | e-mail: laurie@tratt.net github: ltratt twitter: @laurencetratt |
|
August 30 2006 Here is the problem I was facing in a nutshell. In the new version of the Converge language I was making much more use of meta-classes to hide the general ickiness of primitive datatypes. A simple example is the The
class Meta_File(Class):
func new(path, mode):
Con_Obj *new_file_object = malloc(sizeof(
The File class is then defined as:
class File metaclass Meta_File:
func read(num_bytes):
FILE *handle = (((u_char *) self) + sizeof(
It's important to note that, in an ObjVLisp style system, this is really short hand for explicitly creating the class File by calling the new slot in the Meta_File object (which is effectively the Class.new function):
File := Meta_File.new("File", [Object], [func new(): ...])
So far, so good. Now let's assume the user wants to make a subclass of File which sports a new method readline which reads in a line of text rather than a fixed number of bytes. It would seem that this is a reasonable defintion:
class Read_Line_File(File):
func readline():
...
However if one creates an instance of Read_Line_File then Object.new rather than Meta_File_New.new will be used to create the object: no space will be set aside to store the C-level file handle. Fortunately in Converge, while nothing badhappens (i.e. the program doesn't throw a wobbly at the C-level), trying to do anything much with the resulting object will lead to an exception being raised as the VM notices that it is not being given a chunk of memory with a C-level file handle in it. The fix for this is obvious enough: the
class Read_Line_File(File) metaclass Meta_File:
func readline():
...
This makes everything work as expected but, to my mind, is distasteful. There is now a strong coupling between a class, its metaclass, and its subclasses. At best it leads to annoying, easily fixable errors; at worse, it makes refactoring extremely difficult because one has to change the metaclass of each subclass. Although many people do tend to get unduly vexed about coupling of elements within a program - any realistic system is going to have a reasonable degree of coupling, no matter how many patterns etc. one uses - it is better to avoid coupling when possible, especially when it is this pervasive.
So I set about devising a mechanism which would ensure that subclasses would, by default, use the same metaclass as their superclass whilst still being ObjVLisp in spirit (Python's I then started hunting around for all the past work I could find on metaclasses, stumbling along the way on papers that I had seen at some point in the past, but had not been able to digest. Most of what I came across had nothing to say about the issue above, but I saw a couple of references to a concept called What's interesting about the metaclass compatability research is that, once one strips away the theoretical densenes, one discovers that the solutions that are proposed for metaclass compatability effectively present a solution to the problem I outlined earlier in this entry - and which are very similar to what I eventually came up with. My first feeling was of disappointment that I hadn't discovered something novel. My second feeling was also of disappointment: if I'd been able to interpret this research up front, I might have saved myself a lot of effort. But ultimately I realised that I'd merely been a victim of the fundamental problem whenever one discusses theory and practise: even when each route leads to the same answer, that route is often impenetrable to the other side until both independently arrive at the same answer. |
| Home > Technical Articles | e-mail: laurie@tratt.net github: ltratt twitter: @laurencetratt |
| Copyright © 1995-2012 Laurence Tratt | |