Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a scalar as a condition in perl

First timer...so let me know if there is anything that I have not paid attention to whilst posing a question.

The question is how to use a scalar as a condition, as the code below does not work.

my @parameter=('hub');

my %condition;
$condition{'hub'}{'1'}='$degree>=5';

foreach (@parameter) {
       if ($condition{$_}{'1'}) {..}
}

I thought that is because the condition is not interpreted correctly, so I also tried the following, which also did not work.

if ("$condition{$parameter}{'1'}") { ..}

Would really appreciate any help. :)

like image 615
noqa Avatar asked Aug 15 '12 19:08

noqa


3 Answers

You either want string eval, which evaluates a string as Perl code

if (eval $condition{$_}{'1'}) { ...

or perhaps a more secure approach would be using code references

$condition{'hub'}{'1'} = sub { return $degree>=5 };

if ($condition{$_}{'1'}->()) { ...

In the second example, you are attaching a piece of code to a variable. The $var->() syntax executes the code and evaluates to the return value of the code.

like image 84
mob Avatar answered Sep 22 '22 20:09

mob


What you are trying to do is to evaluate '$degree>=5' as real code. Rather than trying to evaluate the string as code (which can be done with eval), it's usually safer and often more robust to instead pass a code-reference. You can use a generator subroutine to generate conditional subs on demand, like this:

sub generate_condition {
    my ( $test, $bound ) = @_;
    return sub { return $test >= $bound; };
}

my %condition;
$condition{'hub'}{'1'} = generate_condition( $degree, 5 );

if( $condition{$parameter}{1}->() ) { ... }

It gets a little more tricky if you want the >= (ie, the relationship itself) to be dynamically created as well. Then you have a couple of choices. One takes you back to stringy eval, with all of its risks (especially if you start letting your user specify the string). The another would be a lookup table within your generate_condition() sub.

generate_condition() returns a subroutine reference that when invoked, will evaluate the condition that was bound in at creation time.

Here's a generalized solution that will accept any of Perl's conditionals and wrap them along with the arguments being tested into a subroutine. The subref can then be invoked to evaluate the conditional:

use strict;
use warnings;
use feature qw/state/;

sub generate_condition {
    my ( $test, $relation, $bound ) = @_;
    die "Bad relationship\n" 
        if ! $relation =~ m/^(?:<=?|>=?|==|l[te]|g[te]|cmp)$/;
    state $relationships = {
        '<'     => sub { return $test <   $bound },
        '<='    => sub { return $test <=  $bound },
        '=='    => sub { return $test ==  $bound },
        '>='    => sub { return $test >=  $bound },
        '>'     => sub { return $test >   $bound },
        '<=>'   => sub { return $test <=> $bound },
        'lt'    => sub { return $test lt  $bound },
        'le'    => sub { return $test le  $bound },
        'eq'    => sub { return $test eq  $bound },
        'ge'    => sub { return $test ge  $bound },
        'gt'    => sub { return $test gt  $bound },
        'cmp'   => sub { return $test cmp $bound },
    };
    return $relationships->{$relation};
}


my $true_condition  = generate_condition( 10, '>', 5 );
my $false_condition = generate_condition( 'flower', 'eq', 'stamp' );

print '10 is greater than 5: ', 
      $true_condition->()  ? "true\n" : "false\n";
print '"flower" is equal to "stamp": ', 
      $false_condition->() ? "true\n" : "false\n";

Often when you construct these sorts of things one is interested in leaving one parameter open to bind at call-time rather than at subroutine manufacture-time. Let's say you only want to bind the "$bound" and "$relation" parameters, but leave "$test" open for specification at subroutine call time. You would modify your sub generation like this:

sub generate_condition {
    my ( $relation, $bound ) = @_;
    die "Bad relationship\n" 
        if ! $relation =~ m/^(?:<=?|>=?|==|l[te]|g[te]|cmp)$/;
    state $relationships = {
        '<'     => sub { return $_[0]  <   $bound },
        # ......

And then invoke it like this:

my $condition = generate_condition( '<', 5 );
if( $condition->(2) ) {
    print "Yes, 2 is less than 5\n";
}

If the goal is to provide late binding of both the lefthand and righthand side in the relational evaluation, this will work:

sub generate_condition {
    my $relation = shift;
    die "Bad relationship\n" 
        if ! $relation =~ m/^(?:<=?|>=?|==|l[te]|g[te]|cmp)$/;
    state $relationships = {
        '<'     => sub { return $_[0]  <   $_[1] },
        '<='    => sub { return $_[0]  <=  $_[1] },
        # ...... and so on .....
    return $relationship->($relation);
}

my $condition = generate_condition( '<' );
if( $condition->(2,10) ) { print "True.\n"; }

This sort of tool falls into the category of functional programming, and is covered in beautiful detail in Mark Jason Dominus's book Higher Order Perl

like image 32
DavidO Avatar answered Sep 21 '22 20:09

DavidO


What are you expecting? String values are interpreted as true when they are nonempty.

themel@kallisti: ~ $ perl -e 'print "oops\n" if "false" ; '
oops
themel@kallisti: ~ $ perl -e 'print "oops\n" if "" ; '
themel@kallisti: ~ $ perl -e 'print "oops\n" if "\$degree < 5" ;'
oops

If you want to dynamically evaluate code in your conditions, you have to investigate eval. Example:

my @conds=('$foo>42', '$foo>23');
my $foo = 33;

foreach my $cond(@conds) { 
    print "$cond itself was true\n" if $cond;
    print "$cond evaluated to true\n" if eval($cond);
}

prints

$foo>42 itself was true
$foo>23 itself was true
$foo>23 evaluated to true
like image 22
themel Avatar answered Sep 24 '22 20:09

themel