Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I conditionally define a Perl subroutine?

Tags:

function

perl

I want to define a Perl function (call it "difference") which depends on a command-line argument. The following code doesn't work:

if ("square" eq $ARGV[0]) {sub difference {return ($_[0] - $_[1]) ** 2}}
elsif ("constant" eq $ARGV[0]) {sub difference {return 1}}

It appears that the condition is ignored, and therefore the "difference" function gets the second definition regardless of the value of $ARGV[0].

I can make the code work by putting a condition into the function:

sub difference {
  if ("square" eq $ARGV[0]) {return ($_[0] - $_[1]) ** 2}
  elsif ("constant" eq $ARGV[0]) {return 1}
}

But this is not really my intention -- I don't need the condition to be evaluated each time during execution. I just need a way to influence the definition of the function.

My questions are:

  1. Why does the first construction not work?
  2. Why does it not give an error, or some other indication that something is wrong?
  3. Is there a way to conditionally define functions in Perl?
like image 280
Ron Avatar asked Feb 25 '09 23:02

Ron


3 Answers

Others have already presented the syntax you requested, but I would recommend using more explicit subroutine references for this, so that you can freely manipulate the reference without manipulating the definition. For example:

sub square_difference { return ($_[0] - $_[1]) ** 2 }
sub constant_difference { return 1 }

my %lookup = (
    'square' => \&square_difference,
    'constant' => \&constant_difference,
);

my $difference = $lookup{$ARGV[0]} || die "USAGE: $0 square|constant\n";
print &$difference(4, 1), "\n";

It's the same basic approach, but I think this syntax will let you map arguments to subroutines a bit more conveniently as you add more of each. Note this is a variation on the Strategy Pattern, if you're into that kind of thing.

like image 71
Ryan Bright Avatar answered Oct 06 '22 22:10

Ryan Bright


What you want to do can be achieved like this:

if ($ARGV[0] eq 'square') {
    *difference = sub { return ($_[0] - $_[1]) ** 2 };
}
elsif ($ARGV[0] eq 'constant') {
    *difference = sub { return 1 };
}
like image 37
Leon Timmermans Avatar answered Oct 07 '22 00:10

Leon Timmermans


I haven't personally done a lot of this, but you might want to use a variable to hold the subroutine:

my $difference;
if ("square" eq $ARGV[0]) {$difference = sub {return ($_[0] - $_[1]) ** 2}}
elsif ("constant" eq $ARGV[0]) {$difference = sub {return 1}}

Call with:

&{ $difference }(args);

Or:

&$difference(args);

Or, as suggested by Leon Timmermans:

$difference->(args);

A bit of explanation - this declares a variable called $difference and, depending on your conditions, sets it to hold a reference to an anonymous subroutine. So you have to dereference $difference as a subroutine (hence the & in front) in order for it to call the subroutine.

EDIT: Code tested and works.

One more EDIT:

Jesus, I'm so used to useing strict and warnings that I forget they're optional.

But seriously. Always use strict; and use warnings;. That will help catch things like this, and give you nice helpful error messages that explain what's wrong. I've never had to use a debugger in my life because of strict and warnings - that's how good the error-checking messages are. They'll catch all kinds of things like this, and even give you helpful messages as to why they're wrong.

So please, whenever you write something, no matter how small (unless it's obfuscated), always use strict; and use warnings;.

like image 37
Chris Lutz Avatar answered Oct 06 '22 23:10

Chris Lutz