Search code examples
systemrakumodulino

How do I make a modulino in Perl6?


I want to make a modulino (a file that can run as either a module or a script) in Perl6.

The following code "processes" file names from the command line:

sub MAIN ( *@filenames )
{
    for @filenames -> $filename
    {
        say "Processing $filename";
    }
}

Saving this as main.pm6 I can run it and it works:

perl6 main.pm6 hello.txt world.txt
Processing 'hello.txt'
Processing 'world.txt'

So, I want this to be a module so that I can add functions and make testing it easier. However, when I add a module declaration to it, it no longer outputs anything:

module main;
sub MAIN ( *@filenames )
{
    for @filenames -> $filename
    {
        say "Processing '$filename'";
    }
}

Which results in nothing output:

perl6 main.pm6 hello.txt world.txt

So, how can I build a modulino in Perl6?

I'm using Perl6 running on MoarVM from the January 2015 release of Rakudo Star.

UPDATE:

When I try wrapping the module in braces:

module main
{
    sub process (@filenames) is export
    {
        for @filenames -> $filename
        {
            say "Processing '$filename'";
        }
    }
};

sub MAIN ( *@filenames )
{
    process(@filenames)
}

I also get errors:

===SORRY!=== Error while compiling main.pm6
Undeclared routine:
    process used at line 14. Did you mean 'proceed'?

Solution

  • The MAIN sub needs to be declared outside the module, but it still must be able to see process.

    There are multiple ways to achieve this, eg by not declaring a module at all

    sub process(@filenames) {
        for @filenames -> $filename {
            say "Processing '$filename'";
        }
    }
    
    sub MAIN(*@filenames) {
        process(@filenames);
    }
    

    by making process our-scoped and calling it by its longname

    module main {
        our sub process(@filenames) {
            for @filenames -> $filename {
                say "Processing '$filename'";
            }
        }
    }
    
    sub MAIN(*@filenames) {
        main::process(@filenames);
    }
    

    or by exporting process and importing it in the body of MAIN

    module main {
        sub process(@filenames) is export {
            for @filenames -> $filename {
                say "Processing '$filename'";
            }
        }
    }
    
    sub MAIN(*@filenames) {
        import main;
        process(@filenames);
    }
    

    In my opinion the most appropriate option is to add MAIN to the module and import it into the script's mainline. This way, everything declared within the module is visible within MAIN without having to explicitly export everything:

    module main {
        sub process(@filenames) {
            for @filenames -> $filename {
                say "Processing '$filename'";
            }
        }
    
        sub MAIN(*@filenames) is export(:MAIN) {
            process(@filenames);
        }
    }
    
    import main :MAIN;
    

    Note that this does not export MAIN by default, ie users of your module will only get it if they provide the :MAIN tag.