How to select subroutine

J

Jan Fure

Hi;

I need to be able to select which subroutine to process a string with
based on the contents of the string.

I have written a code example of this, that uses a lookup table.

Is there a sensible way to do this without the lookup table, i.e., can
I let the parsed string value be the subroutine name?

Jan

#!/usr/local/bin/perl -w
use strict;

my @DATA = <DATA>;
foreach (@DATA) {
chomp;
my @ARGUMENTS = split / /;
my $output = subselect(@ARGUMENTS);
print "The chained output of 2 subroutines is $output\n";
}

sub subselect {
if ($_[0] eq 'sr2') {
my $return_value = sr2(@_);
return $return_value;
}
if ($_[0] eq 'sr3') {
my $return_value = sr3(@_);
return $return_value;
}
}

sub sr2 {
my $x2 = $_[1] ** 2;
return $x2;
}

sub sr3 {
my $x3 = $_[1] ** 3;
return $x3;
}

__DATA__
sr2 3.456
sr3 2
sr2 45
 
B

Ben Morrow

Quoth "Jan Fure said:
I need to be able to select which subroutine to process a string with
based on the contents of the string.

I have written a code example of this, that uses a lookup table.

Is there a sensible way to do this without the lookup table, i.e., can
I let the parsed string value be the subroutine name?

Yes, but it would be a bad idea. A better answer would be to use a
proper lookup table (in Perl, a hash).
#!/usr/local/bin/perl -w

use warnings;

is the modern version on -w.
use strict;

my @DATA = <DATA>;
foreach (@DATA) {

If you want the data line-by-line, then read it line-by line.
chomp;
my @ARGUMENTS = split / /;
my $output = subselect(@ARGUMENTS);
print "The chained output of 2 subroutines is $output\n";
}

sub subselect {
if ($_[0] eq 'sr2') {
my $return_value = sr2(@_);
return $return_value;
}
if ($_[0] eq 'sr3') {
my $return_value = sr3(@_);
return $return_value;
}
}

sub sr2 {
my $x2 = $_[1] ** 2;
return $x2;
}

sub sr3 {
my $x3 = $_[1] ** 3;
return $x3;
}

__DATA__
sr2 3.456
sr3 2
sr2 45

I would do this like (untested):

my %dispatch = (
sr2 => sub { $_[1] ** 2 },
sr3 => sub { $_[1] ** 3 },
);

local $\ = "\n"; # saves printing them all the time
while (<DATA>) {
my @args = split; # this doesn't quite do what you did before,
# but it's likely more flexible and more use.
# And you don't need the chomp.

my $output = $dispatch{$args[0]}->(@args);
print "the output of a sub in a dispatch table is $output";
}

__DATA__
sr2 3.456
sr3 2
sr2 45

I presume 'sr2' and 'sr3' are just examples? If not, I'd extract the '2'
and use it as a parameter rather than writing two separate subs.

Ben
 
J

Jan Fure

Ben said:
Yes, but it would be a bad idea. A better answer would be to use a
proper lookup table (in Perl, a hash).


I would do this like (untested):

my %dispatch = (
sr2 => sub { $_[1] ** 2 },
sr3 => sub { $_[1] ** 3 },
);

Ben
Thanks for replying!

If I understand you right, you are proposing a hash, with keys from my
data, and anonymous subroutines linked to the keys.

For my actual code, this would be quite ugly, as the subroutines are
20+ lines of code.

This was just the shortest code I could come up with, that illustrated
the constraints:
1. Sub routine aquired from data.
2. Ability to pass output to the original function/subroutne call.
I also need the program to exit with appropriate error message, if
there is no match between existing subroutine and requested subroutine,
but I omitted that from the example code to keep it from getting too
long.

Could you elaborate on why it is a bad idea to let the subroutine name
be contained in the data?

Is there a way to improve the structure of the code I wrote, while
keeping the subroutines around? In my production code, I expect there
might be 15+ different sub routines.

Jan
 
T

Tad McClellan

Jan Fure said:
I need to be able to select which subroutine to process a string with
based on the contents of the string.


That is known in computer science as a "dispatch table".

I have written a code example of this, that uses a lookup table.

Is there a sensible way to do this without the lookup table,


No.

(but you can make a much nicer lookup table using a hash and coderefs.)

i.e., can
I let the parsed string value be the subroutine name?


Yes you can, but you don't want to.

That would be using Symbolic References, which should be avoided
when possible...

#!/usr/local/bin/perl -w
use strict;


.... and use strict disallows using symrefs anyway.

my @DATA = <DATA>;
foreach (@DATA) {


Why read all of the lines when you are only going to process one
line at a time anyway?

chomp;
my @ARGUMENTS = split / /;
my $output = subselect(@ARGUMENTS);


my %subs = ( sr2 => \&sr2, sr3 => \&sr3 ); # dispatch table
my $output = $subs{$ARGUMENTS[0]}->(@ARGUMENTS); # call the coderef

sub subselect {
if ($_[0] eq 'sr2') {
my $return_value = sr2(@_);


And here we have the rare case where using an ampersand on a function
call _is_ what you want:

my $return_value = &sr2;

(but passing @_ explicitly is "better" IMO.)

return $return_value;
}
if ($_[0] eq 'sr3') {
my $return_value = sr3(@_);
return $return_value;
}
}
 
I

it_says_BALLS_on_your_forehead

Jan said:
Ben said:
Yes, but it would be a bad idea. A better answer would be to use a
proper lookup table (in Perl, a hash).


I would do this like (untested):

my %dispatch = (
sr2 => sub { $_[1] ** 2 },
sr3 => sub { $_[1] ** 3 },
);

Ben
Thanks for replying!

If I understand you right, you are proposing a hash, with keys from my
data, and anonymous subroutines linked to the keys.

For my actual code, this would be quite ugly, as the subroutines are
20+ lines of code.

This was just the shortest code I could come up with, that illustrated
the constraints:
1. Sub routine aquired from data.
2. Ability to pass output to the original function/subroutne call.
I also need the program to exit with appropriate error message, if
there is no match between existing subroutine and requested subroutine,
but I omitted that from the example code to keep it from getting too
long.

this is called a dispatch table.
 
T

Tad McClellan

Jan Fure said:
Could you elaborate on why it is a bad idea to let the subroutine name ^^^^
be contained in the data?


(because if a bad guy sprinkles the names of "bad" functions in
your data, you will happily execute it for him.
)

Your Question is Asked Frequently:

perldoc -q name

How can I use a variable as a variable name?


See also:

http://www.plover.com/~mjd/perl/varvarname.html
http://www.plover.com/~mjd/perl/varvarname2.html
http://www.plover.com/~mjd/perl/varvarname3.html

and:

http://perl.plover.com/FAQs/Namespaces.html
 
T

Tad McClellan

I also need the program to exit with appropriate error message, if
there is no match between existing subroutine and requested subroutine,


die "appropriate error message" unless exists $dispatch{$ARGUMENTS[0]};
 
B

Ben Morrow

Quoth "Jan Fure said:
Ben said:
Yes, but it would be a bad idea. A better answer would be to use a
proper lookup table (in Perl, a hash).


I would do this like (untested):

my %dispatch = (
sr2 => sub { $_[1] ** 2 },
sr3 => sub { $_[1] ** 3 },
);

If I understand you right, you are proposing a hash, with keys from my
data, and anonymous subroutines linked to the keys.
Yes.

For my actual code, this would be quite ugly, as the subroutines are
20+ lines of code.

FWIW, I don't see how

sub foo {
# 20 lines
}

sub bar {
# 20 lines
}

is substantially less ugly than

my %dispatch = (

foo => sub {
# 20 lines
},

bar => sub {
# 20 lines
},

);

; especially as the subs are all then kept together in an indented
block. However, if you disagree, then you can use

sub foo { ... }

sub bar { ... }

my %dispatch = (
foo => \&foo,
bar => \&bar,
);

just as easily.
I also need the program to exit with appropriate error message, if
there is no match between existing subroutine and requested subroutine,

perldoc -f exists
Could you elaborate on why it is a bad idea to let the subroutine name
be contained in the data?

Think about what happens if you get a key in your data that is the name
of some other sub, not part of your dispatch table.

If your next thought is to create a special package to keep these subs
in, then consider that a package is just a magical hash, and when you
don't need the magic (like now) it's safer, cleaner and faster to just
use a regular hash.

Ben
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,772
Messages
2,569,593
Members
45,111
Latest member
VetaMcRae
Top