2

My program processes two flavors of 'something' and each flavor has its own data structures and procedures for processing them. User invokes the program with either or both of:

  • -f1 path_to_file_with_flavor_1_data
  • -f2 path_to_file_with_flavor_2_data

My program is working coded as:

GetOptions ('f1=s' => \$f1_path,
            'f2=s' => \$f2_path,
           );

if (defined $f1_path) {
  subroutine_to_process_flavor_1_data( $f1_path );
}
if (defined $f2_path) {
  subroutine_to_process_flavor_2_data( $f2_path );
}

It has a single hash to store the processed data for both flavors:

my %flv_hash = ( flavor_1 => { datahash => { ... },
                             },
                 flavor_2 => { datahash => { ... },
                             },
               );

I now want to add each flavor's variable and subroutine names to the hash to make it:

my %flv_hash = ( flavor_1 => { datahash => { ... },
                               var_name => 'f1_path',
                               sub_name => 'subroutine_to_process_flavor_1_data',
                              },
                 flavor_2 => { datahash => { ... },
                               var_name => 'f2_path',
                               sub_name => 'subroutine_to_process_flavor_2_data',
                              },
               );

and change my program to below pseudo-code:

foreach my $flavor ( keys %flv_hash ) {
   if (defined <the variable named $flv_hash{flavor}{var_name}>) {
      <call the subroutine named $flv_hash{flavor}{sub_name}>
   }
}

I have searched all knowledge bases on the subject of storing and retrieving the names of variables and subroutines in a hash but, being a hardware engineer whose software skills are limited to what I learned in Basic Programming 101 that I took some 35 years ago, I couldn't directly copy the examples and get them to work in the context of my program. Another words, if possible I'd appreciate a solution I can just copy and use without having an in-depth knowledge of the Perl paradigms on which they are based. Thank you again.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • 3
    Are you sure you want the _names_ instead of references to the subroutines? `\subroutine_to_process_flavor_1_data` instead of `'subroutine_to_process_flavor_1_data'` is usually more useful. – Ted Lyngmo Jun 20 '20 at 00:45
  • Hi Ted, yes references would be fine, as long as you provide the syntax. – hw_engineer_trying_to_write_sw Jun 20 '20 at 00:50
  • `my $ref = \&subroutine_to_process_flavor_1_data;` (I missed the `&` above). and then call the sub: `&$ref( arguments );` – Ted Lyngmo Jun 20 '20 at 01:01
  • Thank you, Ted. Any idea how to handle the variable in 'if (defined ) ' statement? – hw_engineer_trying_to_write_sw Jun 20 '20 at 01:14
  • 1
    You could use the built in reflection and `eval` the name into a variable but I find that unnecessary 99.99% of the time. Just make `var_name` a reference too, like you did when you used `GetOptions`. Another option is to make `GetOptions` call the subroutine directly: `GetOptions ('f1=s' => \&subroutine_to_process_flavor_1_data, ...`. You can even write the subroutine inline: `GetOptions ('f1=s' => sub { ... }, ...` – Ted Lyngmo Jun 20 '20 at 01:25
  • put your options into a hash and use that instead `GetOptions(\%opts, 'f1=s', 'f2=s') ... my %flv_hash = ...'var_name' => 'f1' ... if (defined $opts{$flv_hash{$flavor}{'var_name'})` – ysth Jun 20 '20 at 03:16
  • 1
    As an alternative to calling a subroutine reference with `&$subref(args)`, most people seem to prefer `$subref->(args)` (as it looks more like how most people use array and hash refs - `$arr_ref->[index]` and `$hash_ref->{key}`). – Dave Cross Jun 22 '20 at 08:34

1 Answers1

5

The one thing that is off in that nice layout is the use of names, for the subroutine and for the variable, in forming the hash -- you can't actually use those things then, to run the subroutine or evaluate the variable, with just their bare names.

Instead, you can take a subroutine reference, which is a scalar and thus can be a hash value, and then the subroutine can be executed by dereferencing it; and use the variable as the value.

The hash

my %flv_hash = ( 
    flavor_1 => { 
        data => { ... },
        var  => $f1_path,
        code => \&subroutine_to_process_flavor_1_data,
    },
    flavor_2 => { 
        data => { ... },
        var  => $f2_path,
        code => \&subroutine_to_process_flavor_2_data,
    },
);

(that code for the key is but a placeholder for a better name).   To use this as indicated

foreach my $flavor ( keys %flv_hash ) {
   if (defined $flv_hash{$flavor}{var}) {
      $flv_hash{$flavor}{code}->( $flv_hash{$flavor}{var} );
   }
}

The \&sub_name syntax takes and returns a reference to a subroutine and so is a scalar, which can be assigned and/or manipulated as any other scalar.

Another way of creating such a code reference is to use an anonymous subroutine, by directly assigning the subroutine code using syntax

my $code_reference = sub { subroutine-code };

what you could also do in your hash (code => sub { ... }) if the subs were short and sweet.

Then the notation $coderef->( LIST ) is how we execute a subroutine with its reference in the scalar variable $coderef. If there are no arguments we need empty parens.

There are of course a number of ways to organize this, once you adopt code references; I merely follow the reasonable intent from the question. Another item that may be useful in this vein is the dispatch table; one recent reference is on this page, and there are many more.

See also for example this post, and for far more this article from The Effective Perler ...etc.


It crossed my mind that perhaps the sub names themselves may be needed as well.

An easy way to retrieve a name from the code reference is with Sub::Util

use Sub::Util qw(subname);

say subname( $coderef );

This is in the core since, I think, perl-5.22.0 (?). There is also Sub::Identify on CPAN.

Then, the almighty B gives that and more via svref_2oject($coderef), which for code reference returns a B::CV object, on which B::GV methods can be used as well

use B qw(svref_2object);

my $cv = svref_2oject($coderef);

say for $cv->FILE, $cv->GV->NAME;  # etc
zdim
  • 64,580
  • 5
  • 52
  • 81
  • zdim, your solution worked like a champ and I learned a few things in the process. Thank you much. – hw_engineer_trying_to_write_sw Jun 20 '20 at 21:17
  • @hw_engineer_trying_to_write_sw Great, I'm very glad for it and thank you for saying that :). Let me know what/if I can add -- it's hard for me to decide what can be useful, but without diluting the post beyond the purpose. (I will add some comments, at least) – zdim Jun 21 '20 at 08:31
  • @hw_engineer_trying_to_write_sw I had added a way to retrieve a name from a code reference. – zdim Jun 28 '20 at 06:56