问题
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