Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Writing portable code

by rovf (Priest)
on Mar 08, 2013 at 09:29 UTC ( [id://1022361]=perlquestion: print w/replies, xml ) Need Help??

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

I have a program which needs to run on Windows and Linux. Most of the code works unchanged on both systems. Only a few sub's are different. There are several solutions for approaching this problems, and I will outline those I have found, below, but since I'm not fully happy with all of them, I'm posting here - maybe someone propses a solution which is more clever than mine.

  1. I could inside the function distinguish, on which platform I'm running:
    sub f { if($^O =~ /Win/) { .... } else { ... } }
  2. I could use subrefs instead of subs:
    my $f = if($^O =~ /Win/) ? sub { ... } else { ... };
    or use a singleton class which bundles the respective subs.
  3. I could package the subs into two modules, and do a use if .... to load one module or the other, depending on the platform.

The last approach seems to me the most elegant, but also the one which is the most complicated (currently, my fairly simple program fits into one file, while with this approach, I would need 3 files). Not really bad, but I wonder, if there is an equally good, or even better, solution, which is simpler. If Perl would have conditional compilation, I would write something like:
# Not real Perl code right now! IF .... sub f { .... } sub g { .... } ELSE sub f{ ... } sub g { ... } END
-- 
Ronald Fischer <ynnor@mm.st>

Replies are listed 'Best First'.
Re: Writing portable code
by tobyink (Canon) on Mar 08, 2013 at 09:51 UTC

    Module::Implementation provides an easy way to load the "best" of multiple implementations of the same function. Its documentation mostly revolves around the situation where you have an XS implementation and a pure Perl implementation, and wish to load the XS if posssible, but fall back to pure Perl. But it works just as well to switch between OS-specific implementations, or Perl-version-specific implementations.

    That said, Perl does have conditional compilation. It's just not very pretty...

    BEGIN { $^O eq 'Win32' ? eval q[ sub f { ... } ] # Win32 implementation : eval q[ sub f { ... } ] # Linux implementation };

    Update: For longer pieces of code, heredocs look quite nice...

    BEGIN { eval($^O eq 'Win32' ? <<'WIN32' : <<'LINUX') }; sub f { print "f-w\n" } sub g { print "g-w\n" } WIN32 sub f { print "f-l\n" } sub g { print "g-l\n" } LINUX

    Update II: Also, bear in mind that constants used in conditionals are optimized away by the compiler, so:

    use constant BROKEN_FORK_IMPLEMENTATION => ($^O eq 'Win32'); sub f { if (BROKEN_FORK_IMPLEMENTATION) { ...; } else { ...; } }

    ... the conditional should be optimized away at compile time, so you don't get the overhead of a string comparison operation every time the function gets called.

    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
      For longer pieces of code, heredocs look quite nice...
      This is great!!!! I did not consider combining several HERE documents, but after re-reading the section in perlop, I understand that for my particular application, this might indeed one way to go!

      -- 
      Ronald Fischer <ynnor@mm.st>
Re: Writing portable code
by Athanasius (Archbishop) on Mar 08, 2013 at 09:57 UTC

    Coming as I do from an OO background, I would approach the problem like this:

    #! perl use strict; use warnings; { package FunctionsFactory; my $class = ($^O =~ /Win/) ? 'WinFunctions' : 'LinuxFunctions'; sub new { return $class->new(); } } { package WinFunctions; sub new { return bless {}, shift; } sub f { print "Windows f()\n"; } sub g { print "Windows g()\n"; } } { package LinuxFunctions; sub new { return bless {}, shift; } sub f { print "Linux f()\n"; } sub g { print "Linux g()\n"; } } my $funcs = FunctionsFactory->new(); $funcs->f(); $funcs->g();

    Output (on my system!):

    19:49 >perl 564_SoPW.pl Windows f() Windows g() 19:53 >

    This scheme allows you to keep all the code in one file, and gives a clean/transparent interface to the functions concerned.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      The disadvantage here is that the WinFunctions package is still parsed and compiled on Linux machines, and the symbol table still exists using up memory, even if it never gets used.

      For a handful of small functions this is unlikely to cause many performance problems.

      What you need to look out for though are cases where, say, WinFunctions not only will not run on Linux, but won't even compile on Linux (e.g. because it uses some Win32::* module). Careful use of run-time require should generally solve this.

      package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name

        Even thinking about having a few unused entries in the symbol table is a ridiculous micro-optimisation.

        If you want to keep the code clean and in one file, then I suggest something like this ...

        use Devel::CheckOS; setup_for_platform(); ... application code goes here ... # decide what platform-specific code to use sub setup_for_platform { if(os_is('MicrosoftWindows')) { *do_stuff = \&do_stuff_for_windows; } elsif(os_is('Unix')) { *do_stuff = \&do_stuff_for_unix; } else { die("Don't know about $^O\n"); } } # platform-specific function implementations sub do_stuff_for_unix { ... } sub do_stuff_for_windows { ... }
Re: Writing portable code
by BrowserUk (Patriarch) on Mar 08, 2013 at 10:46 UTC
    The last approach seems to me the most elegant, but ... I would need 3 files).

    You might look into AutoSplit and AutoLoader.

    Not used much these days, but they seem ideally placed to address your "one file -- only compile the bits I need" requirement.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Writing portable code
by syphilis (Archbishop) on Mar 08, 2013 at 10:23 UTC
    Or (perhaps):

    4. I could use Inline::C or XS:
    void f(void) { #ifdef _WIN32 /* code */ #else /* other code */ #endif }
    This avoids any runtime or compile-time determination of the OS. (It was already determined when the script/module was built.) And only the one file !

    Cheers,
    Rob
Re: Writing portable code
by daxim (Curate) on Mar 08, 2013 at 09:43 UTC
    What do you hope to gain by sticking to having your code in just one file?

      ...simple distribution of the script if you don't build full blown packages. Only a perl script.pl is needed when the script uses only core functionality. That is simple and lightweight. Am I wrong?

      Best regards
      McA

        This *could* be a reason in general, but is not so important in my particular case.

        If the "platform dependent" subs would form a logical unit, which deserves being factored out to a module, this is what I would do.

        -- 
        Ronald Fischer <ynnor@mm.st>

      Since the variations between Windows and Linux are logically part of the module, where they are used, it feels artificial to move them to a different module. That's why I would prefer to keep them together, if (only if!) I find an equally well solution which allows me to do so.

      -- 
      Ronald Fischer <ynnor@mm.st>
Re: Writing portable code
by Anonymous Monk on Mar 09, 2013 at 01:16 UTC

    Perl has neat ways to manage namespaces, so you can avoid singletons, esp for procedural code

    If you're going for conditional compilation optimize the code use Devel::CheckOS();
    use constant WIN32 => !! Devel::CheckOS::os_is('MicrosoftWindows');

    This probably doesn't apply to your codebase, but a common mental block is developing your app in a single file when it naturally lends itself to multiple files

    Write/develop/test the code normally in seperate files -- don't complicate development by limited thinking about distribution :)

    You can combine it into a single file for distribution later, using fatpack/pp/ http://www.cavapackager.com/ or whatever

    PAR/pp can pack your script/modules only without including core modules, but you probably think that's too heavy (requires installing/packing PAR)

    fatpack is lighter, and you can fatpack YourApp::Linux/YourApp::Windows, and offer linuxapp.pl/windowsapp.pl downloads

    or you can use 'cat' with autosplit/autoload like BrowserUk suggests

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1022361]
Approved by marto
Front-paged by Lotus1
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (4)
As of 2024-03-19 05:53 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found