Okay, so I'm really close to making a real release of Contentment following it's latest gutting and rebuilding. This time it looks nice. I owe a lot of the latest bit of nice-ness to Richard Hundt and his nifty Oryx persistence library. I discovered his bit of ingenuity via the CPAN RSS feed.
Anyway, I needed something that Oryx doesn't have (or doesn't have yet): cloning. For my readers which aren't programmers (and haven't already dropped out), cloning is just the word programmers use when we want two copies of the same data. Okay, so after a very small amount of effort I have added cloning.
The reason I need cloning is because I am adding the ability to seamless create object revisions. That is, it's nice when you are building a publishing system to be able to keep old revisions of data around. That way, if someone makes a change to a page that obliterates some important information, you can go back and fetch that information from an old revision of the document. I want this to happen automatically because I don't want folks who are building on to Contentment to have to think to hard about how it works. I just want it to work. I've discovered too often that if something isn't easy to do, it won't be done.
Thus, I want my code to look something like this:
my $obj = Foo->create({ title => 'Foo Version 1.0' });
$obj->title('Foo Version 2.0');
$obj->update;
After that code snippet finishes, there should be two foo objects stored in the database. One will have the title "Foo Version 1.0" and the other with the title "Foo Version 2.0". At the same time, I need to make sure that continued use of "$obj" uses the newer object. Which means that suddenly I have to make sure the entire internal state of that object now refers to the new one. Since the objects use Oryx, this would mean a lot of dinking around with internal state that could change with every new release of Oryx—i.e., a very bad idea.
I could create a special new method for created new revisions, but I don't really want a different syntax when that one I just showed is already crystal clear. The old revision isn't obviously in the database because searching for it will still only return the latest revision unless you specifically want older revisions. I'm not changing the interface so much as the implementation. Why should I muck up the interface?
Enter the power of Perl aliasing. In Perl, alias variables are special variables that don't really have any storage for themselves and exist only as second (or third or fourth...) names for existing variables. Aliases are a lot like references in C++. Also in Perl, variables are passed to subroutines in a special array named "@_". Each element of this array is an alias to the passed information.
For example, if I run the following snippet of Perl:
my $foo = 1; my $bar = 2; foo($foo, $bar, 3);
The special "@_" variable in the foo() subroutine will be set so that $_
package Foo;
# Return a simple empty Foo object
sub new { bless { }, 'Foo'; }
# REPLACE myself with a new Foo!
sub replace_me { $_[0
= bless { replaced => 1 }, 'Foo'; }
package main;
my $foo = new Foo;
print "$foo\n"; # empty Foo object
$foo->replace_me;
print "$foo\n"; # a different Foo object!!!
This is wicked and probably not a good idea in most cases, but it demonstrates how my revisioning code will work. The output of my code should look something like:
Foo=Hash(0x1801434) Foo=Hash(0x18014a0)
Notice that the addresses listed after the type are different! By replacing $_[0] is the replace_me() method, I've overwritten $foo with a different value.
Therefore, all my code needs to do to make everything work after the call to the update() method, is to replace $_[0] with the newly created record object. I don't have to muck with any internal state and the API remains unchanged.
However, this does result in some potentially major-bad pitfalls that I need to be careful about—in fact, they might be bad enough that I'll have to find a different way to do this. However, I thought the concept was very interesting.
Pitfall #1: I better let the user know in the documentation that this happens or they might have problems that I can't anticipate.
Pitfall #2: If I switch the references without updating the original and some data structure uses a reference to the old object, the old object won't be properly updated. I don't think this is a major concern because there's only one place these objects should be directly referenced and I can control and update that one in the process of the update() method. Any other code that needs to create a reference to the object shouldn't be referring to these objects directly, if they are to properly obey the documentation given.
Pitfall #3: There are surely other side-effects that I haven't thought of yet.
I think the Perl aliasing functionality is one of those nifty but dangerous features. We'll see if I actually end up using it the way I've presented here...

Leave a comment