How can I distinguish between an argument that was not passed and one that was passed with a false value?

倾然丶 夕夏残阳落幕 提交于 2019-12-22 04:37:06

问题


I am trying to figure the best way to differeniate in Perl between cases where an argument has not been passed, and where an argument has been passed as 0, since they mean different things to me.

(Normally I like the ambiguity, but in this case I'm generating SQL so I want to replace undefined args with NULL, but leave 0 as 0.)

So this is the ambiguity:

sub mysub {
  my $arg1 = shift;
  if ($arg1){
    print "arg1 could have been 0 or it could have not been passed.";
  }
}

And so far, this is my best solution... but I think it is a little ugly. I'm wondering if you can think of a cleaner way or if this looks OK to you:

sub mysub {
  my $arg1 = (defined shift) || "NULL";
  if ($arg1 ne "NULL"){
    print "arg1 came in as a defined value.";
  }
  else {
    print "arg1 came in as an undefined value (or we were passed the string 'NULL')";
  }
}

回答1:


Here's an example of how you can handle all the possible cases:

sub mysub {
    my ($arg1) = @_;
    if (@_ < 1) {
        print "arg1 wasn't passed at all.\n";
    } elsif (!defined $arg1) {
        print "arg1 was passed as undef.\n";
    } elsif (!$arg1) {
        print "arg1 was passed as a defined but false value (empty string or 0)\n";
    } else {
        print "arg1 is a defined, non-false value: $arg1\n";
    }
}

(@_ is the array of arguments to your function. Comparing it to 1 here is counting the number of elements in the array. I'm intentionally avoiding shift, as it alters @_, which would require us to store the original size of @_ somewhere.)




回答2:


What about:

sub mysub {
    my ( $arg ) = @_;

    if ( @_ == 0 ) {
        print "arg did not come in at all\n";
    } elsif ( defined $arg ) {
        print "arg came in as a defined value.\n";
    } else {
        print "arg came in as an undefined value\n";
    }
}

mysub ();
mysub ( undef );
mysub ( 1 );

Update: I added the check if there was anything passed in at all. But that can only be useful if you are expecting a single parameter. If you would like to get multiple params and need to differentiate between undefined and omitted parameters, take a hash.

sub mysub_with_multiple_params {
    my %args_hash = @_;

    for my $expected_arg ( qw( one two ) ) {
        if ( exists $args_hash{ $expected_arg } ) {
            if ( defined $args_hash{ $expected_arg } ) {
                print "arg '$expected_arg' came in as '$args_hash{ $expected_arg }'\n";
            } else {
                print "arg '$expected_arg' came in as undefined value\n";
            }
        } else {
            print "arg '$expected_arg' did not come in at all\n";
        }
    }
}

mysub_with_multiple_params ();
mysub_with_multiple_params ( 'one' => undef, 'two' => undef );
mysub_with_multiple_params ( 'one' => 1, 'two' => 2 );

And btw: If you have to do any steps to sanitize your params, don't do it yourself. Have a look at cpan and especially Params::Validate




回答3:


personally I like keeping undef to represent NULLs - it matches what DBI placeholders/DBIx::Class/SQL::Abstract all do, and the risk with setting it to be the string "NULL" is that you will accidentally insert the string, rather than NULL itself.

If you're using a recent version of Perl (5.10 or above), then check out the 'defined-or' operators // and //= which are particularly handy for processing arguments.

regarding the SQL, if you want to generate the SQL string, you could end up with something like this:

sub mysub {
  my ($args) = @_;
  my @fields = qw/ field1 field2 field3 /;
  my $sql = "INSERT INTO mytable (field1,field2,field3) VALUES (" .
   join(',', map { ("'".$args->{$_}."'") // 'NULL' ) } )
    .")";
  return $sql;
}

edit (to answer the bit about NULL and undef):

Using DBI handles with placeholders:

my $sth = $dbh->prepare('INSERT INTO mytable (field1,field2,field3) '.
                        'VALUES (?,?,?)');

# undef will set a NULL value for field3 here:
$sth->execute( "VAL1", "VAL2", undef );

DBIx::Class

DBIx::Class - same principle - pass in an undef value to create a NULL in the database:

my $rs = My::Schema->resultset('MyTable');
my $obj = $rs->create({
   field1 => 'VAL1',
   field2 => 'VAL2',
   field3 => undef,    # will set a `NULL` value for field3 here
});



回答4:


The only way to be sure is to inspect the length of @_ to see if there was an argument in that slot. This can be seen as a little complicated when there are also mandatory arguments, but it doesn't have to be. Here's a pattern used in a lot of object accessors:

package Foo;

sub undef_or_unset {
    my ($self, @arg) = @_;

    return 'unset' unless @arg;
    my ($val) = @arg;

    return 'undef' unless defined $val;
    return 'defined';
}

package main;
use Test::More tests => 3;

my $foo = bless {} => 'Foo';

is($foo->undef_or_unset(), 'unset');
is($foo->undef_or_unset(undef), 'undef');
is($foo->undef_or_unset('bluh'), 'defined');



回答5:


map is your friend. Try this:

function("joe",undef); # should print "joe" and "NULL"
function("max",38);    # should print "max" and "38"
function("sue",0);     # should print "sue" and "0"   

sub function {
    my($person,$age) = map { $_ // "NULL" } @_;
    print "person: $person\n";
    print "age:    $age\n";
}

To add a little more color, I've become a fan of using a hash as the arguments for code clarity and to remove the importance of remembering arugment ordering. So rewritten it would look like this:

function2(person=>"joe",age=>undef); # should print "joe" and "NULL"
function2(person=>"max",age=>38);    # should print "max" and "38"

sub function2 {
    my(%args) = map { $_ // "NULL" } @_;
    print "person: $args{person}\n";
    print "age:    $args{age}\n";
}

(Updated: to handle 0 correctly and then again to use the // operator.)



来源:https://stackoverflow.com/questions/8299662/how-can-i-distinguish-between-an-argument-that-was-not-passed-and-one-that-was-p

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!