Search code examples
perlperl-modulesubroutine

Nested modular subroutines where the inner subroutine changes


I have a sub that reads a FASTA text file in chunks.

sub reader {
    foreach my $line (<IN>) {   # read line by line
        chomp $line;
        if ($line =~ m/^>/) {   # if it's a title
            &initiator($title, $seq) unless $firsttitle == 1;
            $firsttitle = 0;
            ($title = $line) =~ s/^>//; # title without > at start
            $seq = '';  # new seq
        } else {
            $seq = $seq . $line;    # append seq lines
        }
    }
    &initiator($title, $seq);   # Do the thing for the last seq.
}

In the middle of several loops, &initiator is called. I'd like to have this in a module that I can "use" but substitute &initiator with other subs from other modules. These subs will need to have their own inputs as well. Would something like the following work or is there a more elegant solution?

use Reader qw(reader);
use Othersub qw(subroutine);
my @par = ('Mary', 'Lamb');
my %functions = (foo => \&Othersub::subroutine);
&reader($file_to_read, $functions{'foo'}($par[0], $par[1]));

Note: Final file structure is Othersub.pm, Reader.pm and the script that uses both modules.


Solution

  • Perl allows you to create references to things, and that includes both subroutines and arrays.

    If you've got differing arguments to pass, then I would suggest you want to do so via array reference rather than what you're doing. A bit like this:

    use strict;
    use warnings;
    
    sub variable_args {
        my ( $code_ref, $array_ref ) = @_;
        #dereference code ref;
        #dereference array ref;
        &$code_ref( @$array_ref, "optional", "extra", "arg" );
    }
    
    sub foo_func {
        foreach (@_) {
            print "Foo $_\n";
        }
    }
    
    sub bar_func {
        print "BAR: ", join( ":", @_ ), "\n";
    }
    
    
    #could inline the functions as anonymous subs. I would avoid doing that
    #unless they're pretty short/clear. 
    my %functions = (
        'foo' => \&foo_func,
        'bar' => \&bar_func,
    );
    
    my %args_to_pass = (
        'foo' => [ "Mary", "Lamb" ],
        'bar' => [ "Some", "Fish", "Pie" ],
    );
    
    
    for my $thing ( "foo", "bar" ) {
        variable_args( $functions{$thing}, $args_to_pass{$thing} );
    }
    

    Note - in the example above, you call &initiator. You shouldn't do this. It's deprecated syntax from Perl 4, and is redundant (and may have some undesired consequences in certain scenarios).

    But I would suggest doing it this way rather than the way you've got. You could get this to work:

    &reader($file_to_read, $functions{'foo'}($par[0], $par[1]));
    

    But what will happen when you try and do that is you'll (potentially) just run your function immediately, and pass it's result into reader.

    E.g.:

    variable_args ( &{$functions{'foo'}}("Mary", "Lamb"), ["more stuff"] );
    

    Won't work, because you're 'running' it immediately, and then sending the result - which'll make your $code_ref whatever the result of the subroutine was.

    However you could make an anonymous sub, and pass that:

    variable_args( sub {
                         &{ $functions{'foo'} }( "Special", "Argument", @_ ) 
                       },
                   $args_to_pass{'foo'} );
    

    I would suggest you're getting needlessly convoluted by that point though :)