tags:

views:

786

answers:

7

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?
+9  A: 

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;.

Chris Lutz
Better syntax for that would be $difference->(args)
Leon Timmermans
+13  A: 

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 };
}
Leon Timmermans
Jonathan Leffler
A typeglob is the way to do this. I would put it in a BEGIN block, though. (Actually, I think the "problem" is braindead, and would never compile things conditionally like this. But with that aside...)
jrockway
@jrockway - why would you put this in a BEGIN block? Is that necessary for any particular reason or is that just your personal style?
Chris Lutz
@Jonathan Leffler: What does "heavy duty" mean in this context?
ysth
I would use BEGIN so that functions declared like this work the sameas ones declared with "sub". (Try calling these without parens to seewhat I mean.)
jrockway
I agree with Jonathan and jrockway (hence I recommended the other answer). This is what the OP wanted to do, not the best way to solve the problem.
Leon Timmermans
You forgot to turn off strict. :)
Schwern
Why would I need to turn off strict?
Leon Timmermans
...good point. :) I'm so used to having to write `*{$class.'::function'} = sub { .. };` that naming code refs and symbolic refs are hard wired in my brain.
Schwern
+6  A: 

Subs are defined at compile time -> if you had "use warnings" enabled, you would have seen an error message about subroutine redefinition.

Mathew
Yes, but there are ways to define subroutines at runtime. See the two posted answers.
Chris Lutz
hmmm, my edit is misssing...You can assigned at runtime - which is indeed what has been suggested. But essentially, subs are defined at compile time (ignoring the "eval" option).
Mathew
Ah well. +1 for recommending "use warnings". I keep forgetting that "strict" and "warnings" are optional.
Chris Lutz
+19  A: 

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.

Ryan Bright
I agree this is a better (more robust) approach (though dieing instead of defaulting to constant_difference would be even better IMHO).
Leon Timmermans
I agree with Leon Timmermans: it may be very bad to assume a default. It might not be a bad idea to try and read a default from a configuration file, but otherwise I would die if they didn't specify.
Chris Lutz
Though I do like it that you make named subroutines and then draw references to them. This seems somehow safer than using anonymous subroutines, especially if you want to overwrite them later.
Chris Lutz
Good point; switched it to a die. Thanks!
Ryan Bright
A: 

Yet another way:

my $diffmode;
BEGIN { $diffmode = $ARGV[0] }
sub difference {
    if ($diffmode eq 'square') { ($_[0] - $_[1]) ** 2 }
    elsif ($diffmode eq 'constant')  { 1 }
    else { "It don't make no never mind" }
}
ysth
Will this be optimized away at compile time, since $diffmode will be known at compile-time? Or is this just the same as the one in his answer?
Chris Lutz
No, not optimized away, but it doesn't depend on a global staying unchanged.
ysth
A: 

Thanks for all the suggestions for how to make the code work. Just for completeness, I'll give the high-level answers to my question.

  1. The first construction doesn't work because functions are defined at compile time but conditions and/or command-line arguments are evaluated at runtime. By the time the condition is evaluated, the named function has already been defined.

  2. The compiler does give a warning with "use warnings", though not one that's very useful for a programmer unaware of 1 :-) The difficulty with giving a meaningful warning is that defining functions inside an if statement can make sense if you also do something with the function within the if statement, as in Leon Timmermans's suggestion. The original code compiles to a vacuous if statement, and the compiler is not set to warn about these.

  3. Strictly speaking, it is not possible to conditionally define functions, but it is possible to conditionally define references (rbright) or aliases (Leon Timmermans) to functions. The consensus seems to be that references are better than aliases, though I'm not quite sure why.

Note about 1: the evaluation order is not obvious until you've actually run into a problem like this; one could envision a Perl which would evaluate conditions at compile time whenever it could be done safely. Apparently Perl doesn't do this, since the following code too gives a warning about a redefined subroutine.

use warnings ;
if (1) {sub jack {}} else {sub jack {}}
Ron
not possible to conditionally define functions *without string eval*
ysth
in the if(1) case, perl does optimize the else away, but that only affects execution; the 2nd sub definition will already have happened
ysth
Leon's code doesn't really create an alias. It installs a named sub into the symbol table just like a normal declaration would. References are usually preferred but there are exceptions. e.g. I've used typeglobs to select between a fast (XS) implementation and fallback on a slow (pure Perl) one.
Michael Carman
#3 is not true, it's just not obvious how to do it. `BEGIN { *foo = sub { 42 } }` and `sub foo { 42 }` are equivalent.
Schwern
A: 

Other answers are correct, using either a code reference or an alias. But the aliasing examples introduce the yicky typeglob syntax and forget to deal with strict.

Alias is an oft forgotten module that wraps up all the magic needed to give a reference a name all while keeping strict.

use strict;
use Alias;

my $difference_method = $ARGV[0];
if( "square" eq $difference_method ) {
    alias difference => sub { return ($_[0] - $_[1]) ** 2 };
}
elsif( "constant" eq $difference_method ) {
    alias difference => sub { return 1 };
}
else {
    die "Unknown difference method $difference_method";
}

And now difference($a, $b) works.

If you only need to call difference() inside your own code, ie. you're not going to export it as a function, I would just use a code reference and forget the aliasing.

my $difference_method = $ARGV[0];

my $Difference;
if( "square" eq $difference_method ) {
    $Difference => sub { return ($_[0] - $_[1]) ** 2 };
}
elsif( "constant" eq $difference_method ) {
    $Difference => sub { return 1 };
}
else {
    die "Unknown difference method $difference_method";
}

$Difference->($a, $b);

Conditionally changing what a function does makes the code harder to follow and less flexible, just like changing behavior on any global. It becomes more obvious when you realize that you're just optimizing this:

my $Difference_Method = $ARGV[0];

sub difference {
    if( $Difference_Method eq 'square' ) {
        return ($_[0] - $_[1]) ** 2;
    }
    elsif( $Difference_Method eq 'constant' ) {
        return 1;
    }
    else {
        die "Unknown difference method $Difference_Method";
    }
}

Any time you have a subroutine of the form...

sub foo {
    if( $Global ) {
        ...do this...
    }
    else {
        ...do that...
    }
}

You have a problem.

Aliasing is most useful for generating similar functions at run time using closures, rather than cut & pasting them by hand. But that's for another question.

Schwern
Why do you have a problem with conditionally changing functions? My program calculates a (big) function over a matrix of data points, and the nature of the data determines how to treat differences between points. What's unclear about manipulating the (little) function that calculates the difference?
Ron
Same issue with global variables. Any part of the code might call difference(). The next guy may not realize difference() changes. By using a lexical code ref $difference you limit the effect to just a block of code AND you make it obvious that it might change. Narrow scopes mean simpler code.
Schwern