Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?

a loop for creating subroutines?

by mascip (Pilgrim)
on Aug 27, 2012 at 16:30 UTC ( #990016=perlquestion: print w/replies, xml ) Need Help??
mascip has asked for the wisdom of the Perl Monks concerning the following question:

Hi all,
how should i do this in Perl :

foreach $sub ( @sub_names ) { sub $sub { #subroutine implementation } }

I'm reading a spreadsheet where there are 19 columns, 14 of which i'm interested in (i'll take the column "LIGHT" as an example). And i'd like to create subroutines with appropriate names, to read each of them.

I know that i could have one subroutine which would take the name of the column as a parameter, like this:

read('light',10); # Retrieve the value of "light" at line 10 read_at_time('light', $timestamp);
But in terms of external API, this seems more readable :
light(10); # Retrieve the value of "light" at line 10 light_at_time($timestamp);
So, how should i implement this?

Replies are listed 'Best First'.
Re: a loop for creating subroutines?
by LanX (Chancellor) on Aug 27, 2012 at 16:33 UTC
    IMHO the cleanest way is a dispatcher table:

    $mysubs{name} = sub { #subroutine implementation }; ... $mysubs{name}->("call","args","whatever");


    another approach with a dedicated package

    *MySubs::name = sub { #subroutine implementation }; ... # and later MySubs::name("call","args","whatever"); # ... or ... package MySubs; name("call","args","whatever");

    (also untested)


    Cheers Rolf

      With this solution, how would my example code look like? Like this?

      $mysubs{light}->(10); $mysubs(light_at_time)->($timestamp);

        I've already shown you three possibilities to call with "arguments".

        I wouldn't try to load unknown subs into main-namespace, that can easily lead to errors.

        Try and see what fits your needs.

        Cheers Rolf

Re: a loop for creating subroutines?
by Anonymous Monk on Aug 27, 2012 at 19:20 UTC

    The keywords you are searching for here are 'partial function application' or 'currying'; both describe the practice of generating a new function with fewer arguments.

    Continuing on LanX's answer, you can give the functions shorter names manually: my $light = $mysubs{'light'}; and then call them with $light->(10);

    Anyway, if you want them installed to the symbol table, you can do this:

    use strict; use warnings; my @sub_names = (qw/foo bar baz/); foreach my $name ( @sub_names ) { no strict 'refs'; *{__PACKAGE__ . "::" . $name} = sub { my ($parm) = @_; print "in $name (got $parm)\n"; } } foo(42); baz(10);

    But be very careful about the content of @sub_names then. Standard cautions may apply, and using a hash is almost always better practice.

Re: a loop for creating subroutines?
by philiprbrenan (Monk) on Aug 27, 2012 at 19:29 UTC
    use feature ":5.14"; use warnings FATAL => qw(all); use strict; use Data::Dump qw(dump); eval "sub $_ {say \"$_\"}" for qw(hello goodbye); hello(); goodbye(); __END__ Produces: hello goodbye

      More solutions, cool :-)

      I see that lanX's answer is the "good" clean answer, but i don't like it as a stand-alone answer, as the code then looks ugly (i forgot to say it's a method, not a function) :

      $S{light}->($self, 10);
      instead of
      (I used %S instead of %mysubs. And i use Moose, so i need to pass $self as an argument).

      So, what i did was to use AUTOLOAD, to call these subroutines :

      sub AUTOLOAD { my $self = shift; my @args = @_; # Retrieve the method name, without the Package name my $name = our $AUTOLOAD; $name =~ s/.*://; # Check if this is one of the functions we declared croak "Undefined method : $name" if !$S{$name}; # Call the appropriate method return $S{$name}->($self, @args); }
      which enables me to call

      Now, the question is : is that clean, and are there "cleaner" (and why) solutions.

      I must say i'm not in love with the 'eval' solution ( but it works :-) ).

      Would it be cleaner to do

      foreach my $name (@names) { my $name = $S{$name}; }
      But then i will also need to put $self as an argument all the time.

      What about installing them in the import table : is it cleaner than using AUTOLOAD ?

        With methods:

        use feature ":5.14"; use warnings FATAL => qw(all); use strict; use Data::Dump qw(dump); sub Mine() {package __SUB__; eval "sub $_ {say \"$_\"}" for qw(hello goodbye); bless {} } my $a = Mine(); $a->hello(); $a->goodbye(); __END__ Produces: hello goodbye

        There is a method to his madness. (Hamlet)

        If it's a class, I would consider both the symbol table and the AUTOLOAD ways good; although AUTOLOAD is safer in the respect that it won't override any existing methods.

        Personally, I'd just install the methods straight into the symbol table as it feels clean and should be slightly more performant. And safety -- you can avoid overwriting any subroutines at install time:

        warn "baz already defined" if defined *{__PACKAGE__ . "::baz" };
        I've shown you two ways and the second works for objects too. (in perl package == class)

        Don't know too much about moose.

        But if you ask for "the cleanest" approach, better avoid metaprogramming!

        Just define two getters and use a fat comma to get rid of the quoting.

        $object->cell(light => 10); $object->time(light => $timestamp);

        I like metaprogramming, but here it's over the top.

        And this certainly works with moose too!

        Cheers Rolf

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://990016]
Approved by stonecolddevin
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (7)
As of 2017-02-21 19:33 GMT
Find Nodes?
    Voting Booth?
    Before electricity was invented, what was the Electric Eel called?

    Results (317 votes). Check out past polls.