Internal-use-only class in a deeply nested Perl module - avoiding awkward naming

Viewed 133

Suppose a Perl module named MyApplication/Subcomponent.pm, and like usual the file starts with package MyApplication::Subcomponent; This module happens to define a collection of utility functions. One of those utility functions needs to create an instance of a local, internal-use-only class. The class definition is very simple so I'll just paste it here. It's a RAII class that saves and restores the value of an environment variable.

{
  package MyApplication::Subcomponent::restore_ENV_THING;

  sub DESTROY {
    my $self = shift;
    $ENV{THING} = ${ $self };
  }

  sub new {
    my $class = shift;
    my $old_value = $ENV{THING};
    $ENV{THING} = shift;
    return bless \$old_value, $class;
  }
}

It's my understanding that I have to give this class a name in the global space of package names, and I have to name it starting from the root: I can't just write package restore_ENV_THING; because that would stomp on the top level of the namespace.

So here's the question: What, if anything, could I do so that the code that uses this class -- which is guaranteed to be in the same file as the above code block and lexically below it -- could be written

sub utility_routine_that_needs_to_save_and_restore_THING {
  my $restorer = restore_ENV_THING->new($temporary_value);
  ...
}

instead of

sub utility_routine_that_needs_to_save_and_restore_THING {
  my $restorer = MyApplication::Subcomponent::restore_ENV_THING
    ->new($temporary_value);
  ...
}

If you know a trick that would allow me not to have to give the class a name in the global space of package names (perhaps something not entirely unlike what open my $fh, ... does?) I would like to hear about that too.


Note that MyApplication has some unusual constraints on its code:

  • The oldest supported version of Perl is 5.006. Yes, you read that right, 5.006. In particular this means I cannot use lexically scoped subs.
  • No CPAN modules may be used, only modules that were included in the stdlib as of 5.006. This is why I don't just use Guard or Scope::Guard, and why I am manually defining the class using primitives.
  • Cannot ship any XS code itself. This is why I am using sub DESTROY for RAII even though that won't work right if the guard object is mishandled in the caller; as far as I know, there's no way to do better without dropping down to C.
2 Answers

Would a package alias do?

{
  package MyApplication::Subcomponent::restore_ENV_THING;

  BEGIN {
      *MS:: = *MyApplication::Subcomponent::;  # (initials for name)?
  };

  sub DESTROY {
    my $self = shift;
    $ENV{THING} = ${ $self };
  }

  sub new {
    my $class = shift;
    my $old_value = $ENV{THING};
    $ENV{THING} = shift;
    return bless \$old_value, $class;
  }
}

Now it can be used as MS::restore_ENV_THING.

This is global over all packages and scopes, being a compile time alias. It does muck with the main:: symbol table but that should be fine as long as you pick a "free" name.

And then there are packages, like aliased pragma, Package::Alias, and a number of others, but they do come with subtleties so I'd recommend reading up on them first. I found the builtin aliasing as shown above a fine solution whenever I used it.

MyApplication::Subcomponent::restore_ENV_THING->new($temporary_value);

is short for

"MyApplication::Subcomponent::restore_ENV_THING"->new($temporary_value);

so all you need is

use constant restore_ENV_THING => "MyApplication::Subcomponent::restore_ENV_THING";

restore_ENV_THING->new($temporary_value);
Related