Asymmetrical View

Idempotency or Singleton Memoization in Perl

This is an example of a factory for creating a function who’s body will only fire once, returning the first computed result each time it is invoked thereafter.

sub makeDoOnce {
  my($sub) = @_;
  my $alreadyDone = undef;
  my @result      = undef;
  my $exception   = undef;
  return sub {
    die $exception if $exception;
    if ($alreadyDone) {return wantarray ? @result : $result[0];}

    eval {
      my $w = wantarray;
      if (not defined $w) {             $sub->(@_)}
      if ($w)             {@result    = $sub->(@_)}
      else                {$result[0] = $sub->(@_)}
    };
    $exception = $@ if $@;
    die $exception if $exception;
    $alreadyDone = 1;
    return wantarray ? @result : $result[0];
  };
}

This works by creating a closure over the $alreadyDone, @result and $exception variables. Within the returned sub, any exception is re-thrown, if the result was previously computed, it is returned. If no exception or previous result was calculated, then the original function is invoked, storing off the exception or result and returning or throwing as appropriate.

An example usage is:

  my $getStartTime = makeDoOnce(sub { time });
  print "We started at: ", scalar(localtime $getStartTime->()), "\n";
  sleep 5;
  print "We started at: ", scalar(localtime $getStartTime->()), "\n";

I often use this pattern for one-time initializations (loading plugin systems, ensuring file structures exist, etc), where I want the time of call to be flexible but the action it performs to happen only one time.

Kyle Burton, 18 Jun 2008 – Wayne PA

Tags: