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

Sorry for the bad title, but I couldn't think of something more appropriate. I'm writing (yet another) system info gatherer. My plan is to support multiple operating systems in the same script, but I don't want to do "if ($os = 'foo') { ... }" for every subroutine, nor do I want to have a separate sub for each OS. I know that the following code doesn't work, but I hope it serves as an example of what I am trying to accomplish:
#!/usr/bin/perl -w BEGIN { if ( $^O =~ /dar/ ) { use mac } elsif ( $^O =~ /sol/ ) { use sun } elsif ( $^O =~ /aix/ ) { use aix } else { die "unknown os." } }; package main; print "answer is " . get_answer() . "\n"; exit 0; package mac; sub get_answer { return "Apple" } package sun; sub get_answer { return "Sun" } package aix; sub get_answer { return "IBM" } __END__
Is there some way I can make this work?

Replies are listed 'Best First'.
Re: Using a 'package' as a 'module', and choosing at runtime?
by PodMaster (Abbot) on Oct 28, 2003 at 07:09 UTC
    If you know what use does (perldoc -f use, perldoc -f require) ... an example is File::Spec (a core module)
    package File::Spec; use strict; use vars qw(@ISA $VERSION); $VERSION = '0.86'; my %module = (MacOS => 'Mac', MSWin32 => 'Win32', os2 => 'OS2', VMS => 'VMS', epoc => 'Epoc', NetWare => 'Win32', # Yes, File::Spec::Win32 works on Ne +tWare. dos => 'OS2', # Yes, File::Spec::OS2 works on DJGP +P. cygwin => 'Cygwin'); my $module = $module{$^O} || 'Unix'; require "File/Spec/$module.pm"; @ISA = ("File::Spec::$module"); 1; __END__
    Sure you don't need to actually require/use anything, but @ISA is @ISA ;)(perldoc perlmod, perldoc perlvar ...)

    MJD says "you can't just make shit up and expect the computer to know what you mean, retardo!"
    I run a Win32 PPM repository for perl 5.6.x and 5.8.x -- I take requests (README).
    ** The third rule of perl club is a statement of fact: pod is sexy.

      require is definitely a good and straight answer to the question, and I believe this must be one of the original reasons made them to have require in Perl in the first place. As sometime, you simply either cannot determine what to use at compile time, or you cannot determine whether you even require anything.

      The other way seems also very natual is to have a base class and those platform-specific classes subclass it. Less efficient, but chance is that there are common attributes across platforms, and could be a pretty neat design.

      Actually should be a mixture of both, a neat OO design with dynamically loading of subclasses.

Re: Using a 'package' as a 'module', and choosing at runtime?
by Abigail-II (Bishop) on Oct 28, 2003 at 09:44 UTC
    I'd put the various packages in different files. For instance, the file mac.pm:
    package mac; use strict; use warnings; use Exporter (); our @EXPORT = qw /get_answer/; our @ISA = qw /Exporter/; sub get_answer { "mac"; } 1; __END__

    And then the main program:

    #!/usr/bin/perl use strict; use warnings; BEGIN { if ( $^O =~ /dar/ ) { require mac; mac -> import; } elsif ( $^O =~ /sol/ ) { require sun; sun -> import; } elsif ( $^O =~ /aix/ ) { require aix; aix -> import; } else { die "unknown os." } }; print "answer is " . get_answer () . "\n"; __END__

    Abigail

Re: Using a 'package' as a 'module', and choosing at runtime?
by ptkdb (Monk) on Oct 28, 2003 at 13:01 UTC
    You could try using inheritance and polymorphism

    our $Type ; BEGIN { if ( $^O =~ /dar/ ) { $Type = 'Mac' ; } elsif ( $^O =~ /sol/ ) { $Type = 'Sun' } elsif ( $^O =~ /aix/ ) { $Type = 'Aix' } else { die "unknown os." } }; package base ; sub common_routine {} sub specific_routine { die "must be one of mac, aix, sun" ; } sub package Mac ; our(@ISA) = qw/base/ ; sub specific_routine { DoMacStuff()} package Aix; our(@ISA) = qw/base/ ; sub specific_routine { DoAIXStuff()} package Sun ; our(@ISA) = qw/base/ ; sub specific_routine { DoSunStuff()} package main ; my($obj) = new $Type ; $obj->specific_routine ;

    Nuts and Bolts

    We start with a 'base' package. This will contain all of the methods that are common to all the platforms. Then for each supported platform we create a new package, and declare within it the @ISA variable(this package 'is a' ...) so that when perl wants to find a method when doing $obj->method_name, it will first check the objects package, if it doesn't find it there, it will go through each package in the @ISA list, and execute the first 'method_name' it finds. This is how perl does OOP polymorphism, the facility for different classes to have different implementations for the same 'method_name'.

    For each method, that will need a different implementation for a given platform, we'll first add that method to the base package, which may just include a 'die' to remind us to implement this method for this platform we're testing on, and then add the method in each specific platform.

    It possible that some methods may service several platforms, and one just needs it a little differently, in which case we can put the common method in the 'base' package, and the one that needs to be different in its own pacakge, thus overriding it.

Re: Using a 'package' as a 'module', and choosing at runtime?
by Roger (Parson) on Oct 28, 2003 at 06:24 UTC
    If I understand correctly, you want to have multiple modules together with the main script all in the same file. One way I can think of, without excessive rewrite, is by using a scalar reference to point to required package code at run-time, sort of a poor-man's dynamic link library.

    #!/usr/bin/perl -w my $pkg; BEGIN { if ( $^O =~ /dar/ ) { $pkg = \&mac::get_answer } elsif ( $^O =~ /sol/ ) { $pkg = \&sun::get_answer } elsif ( $^O =~ /aix/ ) { $pkg = \&aix::get_answer } else { die "unknown os." } }; print "answer is " . &$pkg() . "\n"; exit 0; package mac; sub get_answer { return "Apple" } package sun; sub get_answer { return "Sun" } package aix; sub get_answer { return "IBM" } __END__

      References to package subroutines are always done symbolically (even with strict 'refs' on), so you only need to change how the package is called.

      #!/usr/bin/perl -w my $pkg; BEGIN { if ( $^O =~ /dar/ ) { $pkg = 'mac' } elsif ( $^O =~ /sol/ ) { $pkg = 'sun' } elsif ( $^O =~ /aix/ ) { $pkg = 'aix' } else { die "unknown os." } }; print "answer is " . $pkg->get_answer() . "\n"; exit 0; package mac; sub get_answer { return "Apple" } package sun; sub get_answer { return "Sun" } package aix; sub get_answer { return "IBM" } __END__

      Which makes it a lot easier to add new class methods in the future.

      ----
      I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
      -- Schemer

      :(){ :|:&};:

      Note: All code is untested, unless otherwise stated

        Excellent! Thanks so much; this looks exactly like what I want to accomplish.
Re: Using a 'package' as a 'module', and choosing at runtime?
by BrowserUk (Patriarch) on Oct 28, 2003 at 07:30 UTC

    You could do something like

    #! perl -slw use strict; use warnings; package xtest::mac; sub get_answer{ 'Apple' } package xtest::sun; sub get_answer{ 'Sun' } package xtest::aix; sub get_answer{ 'IBM' } package xtest::win; sub get_answer{ 'Win' } package xtest; if( $^O =~ m[(dar|sol|aix|win)]i ) { ## Export to main for testing only. *main::get_answer = \&{"xtest::\L$1\E::get_answer"}; } else { die 'Unknown OS'; } 1;

    Then

    P:\test>perl -Mxtest -e"print get_answer()" Win

    If you have lots of vaient subs, then you can put the platform specific packages in separate modules, use the standard export mechanism and require them from the base package and then call test::$1::export(); to get the exports into your package.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Hooray!