7

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'?
Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
Christopher Bottoms
  • 11,218
  • 8
  • 50
  • 99
  • it should be enough to put it outside the module, ie `module main { ... }; sub MAIN { ... }` – Christoph Mar 26 '15 at 15:47
  • @Christoph It didn't work, but thanks for the suggestion. Is this a bug? – Christopher Bottoms Mar 26 '15 at 16:02
  • Not a bug, but a consequence of lexical scoping. There are several ways to structure your code: Declare `process` outside the module, make it `our`-scoped and call is as `main::process` or export it and add an `import` statement to the body of your sub `MAIN` – Christoph Mar 26 '15 at 16:05

1 Answers1

8

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.

Christoph
  • 164,997
  • 36
  • 182
  • 240