Maybe it’s a sign I’ve been spending too much time playing with javascript lately, but when I revisited some code I’d written in Perl recently I suddenly realised that the tangled web of private object initialisers I was staring at would be so much simpler if I used lazy initialisation instead. Javascript makes lazy initialisation easy because its a functional programming language:
function lazy() {
var lazyVar = "initialValue";
lazy = function() {return lazyVar;};
return lazyVar;
}
console.log("Before first call: " + lazy.toString());
console.log("First call returns: " + lazy());
console.log("After first call: " + lazy.toString());
console.log("Second call returns: " + lazy());
If you run the above in a javascript console such as Firebug, you’ll see that after the first call which initialises lazyVar, the function is redefined to be the closure which eliminates the initialisation step on all subsequent calls.
So anyway, I started wondering how I’d achieve the same thing in Perl. It’s easy enough to see how you’d cause the initialisation to be lazy (without actually redefining the subroutine to speed up subsequent calls):
sub lazySub {
my $self = shift;
my $priv = "_lazy";
return $self->{$priv} if exists $self->{$priv};
return $self->{$priv} = 'initialValue';
}
The above is a handy template, but things would be a lot easier to use if we encapulated the above into a subroutine of its own:
sub lazyInit(\&) {
my $self = shift;
my $priv = shift;
my $f = shift;
return $self->{$priv} if exists $self->{$priv};
return $self->{$priv} = &$f;
}
sub lazy {
my $self = shift;
return $self->lazyInit("_lazy", sub {
return 'initialValue';
});
}
Now that’s looking better, we’re passing in a string to use as the private identifier and an anonymous subroutine to use as the initialiser.
Now when I actually started using this, I realised that the private identifier that I was using was always the name of the subroutine (with an underscore prefix to indicate that it’s private). So I eliminated the first argument altogether by using caller();
sub lazyInit(\&) {
my $self = shift;
my $f = shift;</code>
my $priv = "_" . (split("::", (caller(1))[3]))[-1];
return $self->{$priv} if exists $self->{$priv};
return $self->{$priv} = &$f;
}
sub lazy {
my $self = shift;
return $self->lazyInit(
sub {
return "initalValue";
}
);
}
That’s all well and good, but what if we want to be able to overwrite the value? Or force the initialising function to be called again?
sub lazyInit(\&) {
my ($self, $initialiser, %args) = @_;
my $priv = "_" . (split("::", (caller(1))[3]))[-1];
if (exists $args{overwrite}) {
return $self->{$priv} = $args{overwrite};
} elsif (not exists $self->{$priv} or defined $args{forceReload}) {
return $self->{$priv} = &$initialiser;
} else {
return $self->{$priv};
}
}
sub lazy {
my ($self, %args) = @_;
return $self->lazyInit(
sub {
return "lazyValue";
}, %args
);
}
$self->lazy; # get value
$self->lazy(overwrite => 'newLazyValue'); # overwrite value
$self->lazy(reload => 1); # cause initialisation subroutine to be called again, returning value to 'lazyValue'
And you’re free to use other paramaters of your own chosing in the anonymous closure (just don’t use ‘overwrite’ or ‘forceReload’ to avoid conflicts!)
Finally, if the value only needs to be read-only, we can redine the subroutine after its first use (yes you can do this in Perl!) so that subsequent calls don’t require the unnecessary (if defined) test:
sub lazyInit(\&) {
my $self = shift;
my $f = shift;
my $overrideName = shift;
my $priv;
my $method;
if (defined $overrideName) {
$priv = "_" . (split("::", $overrideName))[-1];
$method = $overrideName;
} else {
$priv = "_" . (split("::", (caller(1))[3]))[-1];
$method = (caller(1))[3];
}
# $self->debug("slow - $method");
$self->{$priv} = &$f;
{
no warnings 'redefine';
no strict 'refs';
*{ $method } = sub {
# $self->debug("fast - $method");
return $self->{$priv};
};
}
return $self->{$priv};
}
If you uncomment the comments and run this, you will see that the first time it is run “slow – $method” is printed, but on all subsequent calls “fast – $method” is called. I’ve also added the ability to override the method name used, which actually lets you achieve read-write if you call lazyInit() from a different function and have it redifine a suboutine of your chosing.
Cool huh?
