dsheroh has asked for the wisdom of the Perl Monks concerning the following question:

Preamble: I'm fully aware that this may be an XY Problem and that there may well be a better approach that I should be taking; if so, feel free to suggest better approaches. However, I find the way I'm doing it now to be interesting and would still like to find the answer, even if only for the sake of curiosity.

The Immediate Question: When inserting a code reference into @INC (as described a bit more than halfway down the require docs), what should you return from your code to indicate to require that the request has been fully handled and it should not continue checking @INC in search of the file?

Based on the docs, it seems to me that return (undef, \&{sub { 0 }} ); should do the trick (second value returned is a sub reference which returns 1 for each line of source code provided and 0 at EOF), but neither this nor any of the variations on it that I've tried have convinced require that its job has been done.

The Overall Picture:

I'm in the process of redesigning a system which makes heavy use of installations being able to override the standard versions of its modules by having a lib/default for the standard version and lib/extension for the local version. Just having lib/extension first in @INC does a decent job of this, but has the drawback of requiring the lib/extension version to be a complete copy of what's in lib/default, even if only one sub needs to be overridden.

In an attempt to allow the local version to get by with only respecifying the changed subs, I've gone off and written a sub which goes at the head of @INC and, when module Foo is used/required, first loads Foo, then loads Local::Foo on top of it. This much works (aside from some warnings about redefined subroutines which no warnings 'redefine' doesn't seem to silence).

Unfortunately, after I've finished this, the normal require processing then loads module Foo a second time, reverting everything back to the non-Local version (and generating another batch of warnings, this time for every sub in Foo), which kind of defeats the purpose of the whole thing.

As for whether this should be done at all, we do plan in the redesign to add a lot more data-driven configurability and some hooks for callbacks in order to minimize the need for wholesale replacement of subs, but things are expected to vary enough from one installation to another that the capability still needs to be there.

This does sound an awful lot like OO polymorphism, and my first stab at it was based on using OOP and either constructors or factories which detect the presence of Local subclasses and return them instead of the base class, but I don't expect to see a top-to-bottom pure-OO architecture in the new version, so we'll likely need something that works with both OO and non-OO code, which is how I got started down the current path. (No pun intended.)

Replies are listed 'Best First'.
Re: Telling require "I've handled it"
by JavaFan (Canon) on Oct 29, 2010 at 14:32 UTC
    When inserting a code reference into @INC (as described a bit more than halfway down the require docs), what should you return from your code to indicate to require that the request has been fully handled and it should not continue checking @INC in search of the file?
    I think the docs are mistaken. It should either return a file handle, or a sub, not "not-a-filehandle" and a sub.
    use 5.010; use lib sub {say "Using $_[1]"; sub {$_ = "1;"; 0}}, sub {die}; use Blah; say "Hi!"; __END__ Using Blah.pm Hi!
      That's a step in the right direction, at least, but return sub { $_ = "1;"; 0; }; (with or without the explicit return) gets me
      Foo.pm did not return a true value at -e line 1.
      ...which is very odd, seeing as that's the exact same thing you're returning from your example code. Requiring any combination of Foo and Local::Foo without my sub in place works fine, so I'm fairly confident that the problem is with the sub, not the modules being loaded.

      In case there's some other error I'm missing, here's the current version of the sub I'm inserting into @INC:

      BEGIN { unshift @INC, \&require_local } sub require_local { # Exit immediately if we're not processing overrides return if $core_only; my (undef, $filename) = @_; # Keep track of what files we've already seen to avoid infinite lo +ops return if exists $already_seen{$filename}; $already_seen{$filename}++; # We only want to check overrides in $base_namespace return unless $filename =~ /^$base_namespace/; # OK, that all passed, so we can load up the actual files # Get the original version first, then overlay the local version say STDERR "requiring $filename"; require $filename; my $local_file = $filename; if ($base_namespace) { $local_file =~ s[^$base_namespace][${base_namespace}/Local]; } else { # Empty base namespace is probably a bad idea, but it should b +e # handled anyhow $local_file = 'Local/' . $local_file; } $already_seen{$local_file}++; say STDERR "requiring $local_file"; # Failure to load local version is not fatal, since it may not exi +st no warnings 'redefine'; eval { require $local_file }; say STDERR "Done."; return sub { $_ = "1;"; 0; }; }
      All three messages are appear on STDOUT, along with the subroutine redefined warning for the sub I've duplicated in Local/Foo.pm, so the sub is definitely running all the way through. It just seems to be returning a false value. (I also tried return sub { $_ = "1;"; 1; }; and, as I expected, it went into an infinite loop waiting for the returned sub to declare EOF.)
      What version of perl did you test that with? On
      This is perl, v5.10.1 (*) built for i686-linux-gnu-thread-multi (with 40 registered patches, see perl -V for more detail)
      (it's the default system perl from latest Ubuntu) your example doesn't seem to be working for me:
      $ perl -E 'use lib sub {say "Using $_[1]"; sub {$_ = "1;"; 0}}, sub {d +ie}; use Blah; say "Hi!";' Using Blah.pm Blah.pm did not return a true value at -e line 1. BEGIN failed--compilation aborted at -e line 1.
        Tested to be working on all versions of 5.12.
Re: Telling require "I've handled it"
by jethro (Monsignor) on Oct 29, 2010 at 16:35 UTC

    As I understand the require-docs, returning undef as first parameter will automatically lead to continuation of @INC inspection:

    If an empty list, undef, or nothing that matches the first 3 values above is returned, then require looks at the remaining elements of @INC

    You should either give it a file handle as first parameter, or no first parameter at all

      Please expand on how you would go about returning a second parameter with "no first parameter at all". As far as I can tell, (undef, ...) is identical to (but more convenient than) creating an empty array and setting its second element while leaving the first untouched:
      #!/usr/bin/env perl use Data::Dumper; my @a1 = (undef, 'second'); my @a2; @a2[1] = 'second'; print Dumper(\@a1, \@a2); __END__ Outputs: $VAR1 = [ undef, 'second' ]; $VAR2 = [ undef, 'second' ];
        Ah yes, I should have been more exact. What I meant was giving the subroutine pointer as first parameter, leaving out the nominal first paramter, the filehandle, completely. In other words no undef at all and the subroutine reference is promoted to first parameter.
Re: Telling require "I've handled it"
by Anonymous Monk on Oct 29, 2010 at 16:47 UTC
      Ah, good find! I'll definitely take a look at the tests to see what enlightenment they might convey.
      Going through inccode.t, I was able to get it working by returning an in-memory filehandle pointed to the string "1":
      use PerlIO::Scalar; # to avoid recursion when creating $fh sub require_local { # do the actual work, per the source I posted earlier # ... open my $fh, '<', \'1'; return $fh; }
      Thanks for finding those tests and pointing me at them!