Why is "!!" considered bad form in Perl?
During a recent job interview process, I submitted some sample Perl code which used the so-called "secret" !!
operator. Later, when discussing the code, one of the interviewers asked me why I chose to use that, and indicated that it was considered bad form. He didn't elaborate as to why.
My team and I have been using this operator for years, without ever realizing it was considered "bad form."
Does the "bang bang" operator have side-effects or other unexpected behavior? Why is it, or might it be, considered "bad form" by some? Is there an idiomatic alternative?
Below are a few examples where I would have considered !!
acceptable and/or desirable.
-
The actual code in the coding exercise, which is an example of adding booleans:
while (my $line = <$F>) { # snip exists $counts{lines} and $counts{lines} += !! chomp $line; }
-
Using a boolean value as a hash key (clearly a simplified example):
sub foo { my ($input) = @_; my %responses = ( '' => "False", 1 => "True" ); return $responses{ !! $input }; }
-
Using a boolean in a bitwise operation, or even
pack()
:sub foo { my ( $a, $b, $c ) = @_; my $result = !!$a + (!! $b)<<1 + (!! $c)<<2; return $result; }
-
You need to do a typecast for use by an external library/process, such as a database, which only considers certain values to be truthy:
my $sth = $dbh->prepare("INSERT INTO table (flag,value) VALUES (?,?)") $sth->execute("i_haz_cheeseburger", !! $cheeseburger_string)
I'd call it 'bad form' because it simply isn't necessary. You don't need to boolean convert in Perl. And so at best it's redundant, and at worst it's an obfuscation.
While there are some really nice tricks you can use in Perl, one of the biggest problems with the language (perceptually at least) is that it's rather prone to "write once" code.
After all - why do you ever need to 'double-negate' a scalar to get a boolean, when you can just test the scalar?
my $state = !! 'some_string';
if ( $state ) { print "It was true\n" };
Or:
if ( 'some_string' ) { print "It was true\n" };
And it's true - you can write some horrifically unintelligible Perl code thanks to "secret" operators, punctuation-based variables, etc. And it'll work fine - for example - I still consider this a masterpiece: 3-D Stereogram, Self replicating source
But it isn't 'good code'. For 'real world' usage, your code needs to be clear, intelligible and easy to troubleshoot.
Regarding alternatives;
while (my $line = <$F>) {
# snip
exists $counts{lines} and $counts{lines} += !! chomp $line;
}
This is ... counting how often you've successfully chomp
ed a line I think? So basically it just counts numbers of lines in your input.
For that, I'd be thinking 'use $.
'. Unless I've missed something profound about your code?
"What if it's not chomp?"
Well, OK. How about:
$counts{lines}++ if foo();
For this:
sub foo {
my ($input) = @_;
my %responses = ( "" => "False", 1 => "True" );
return $responses{ !! $input };
}
I'd be writing that as:
sub foo {
my ($input) = @_;
return $input ? "True" : "False";
}
For the last condition - packing flags into a byte:
sub flags {
my @boolean_flags = @_;
my $flags = 0;
for ( @boolean_flags ) {
$flags<<=1;
$flags++ if $_;
}
return $flags;
}
print flags ( 1, 1, 1 );
Or perhaps if you are doing something like that - taking a leaf from how Fcntl
does it by defining values to add based on position, so you're not having to worry about the positional arguments problem:
You can request that the flock() constants (LOCK_SH, LOCK_EX, LOCK_NB and LOCK_UN) be provided by using the tag
:flock
.
And then you can:
flock ( $fh, LOCK_EX | LOCK_NB );
They're defined bitwise, so they 'add' via the or
operator - but that means it's an idempotent operation, where setting LOCK_NB
twice wouldn't be.
For example,
LOCK_UN = 8
LOCK_NB = 4
LOCK_EX = 2
LOCK_SH = 1
If we expand the question to the less subjective:
I'm asking for the reasons to avoid !!
- Perl evaluates "truth" of variables, so there's not really much need for an actual logical boolean.
- Conditional statements are clearer than their equivalent boolean algebra.
if ( $somecondition ) { $result++ }
is clearer than$result += !! $somecondition
. - It's on a secret operator list because it's obscure. Your future maintenance programmers may not appreciate that.
-
!!1
is1
,!!0
isdualvar('', 0)
. While that's quite flexible, it can surprise, especially since most people don't even know what a dualvar is, much less that!
returns one. If you use a ternary, it's explicit what values you get:$value ? 1 : 0
Your !!
takes advantage of two obscure things in Perl: The specific values that !
returns, and that one of the possible values is dualvar (a scalar containing both a string and a number). Using !!
to compound these behaviors is admittedly pithy. While its pithiness can be a huge plus, it can also be a rather big minus. Don't use features that result in a project mandating that "the use of Perl is forbidden on this project."
There are lots of alternatives to $count += !! (some_expression)
to count occurrences of truthy values of that expression. One is the ternary, $count += (some_expression) ? 1 : 0
. That too is a bit obscure. There is a nice compact way to do what you want, which is to use the post-if:
$x++ if some_expression;
This says exactly what you are doing.
The whole question is somewhat subjective, so I'd like to give an opinion that differs from the other answers posted so far. I wouldn't consider double negation bad form in general, but I'd try to avoid it if there's a more readable alternative. Regarding your examples:
Use something like
$count++ if $condition
.Truth values as hash keys? That's simply evil. At least, it's surprising for anyone who has to maintain your code.
Here the use of
!!
is justified.Here I'd use the ternary operator. It isn't really clear how
execute
behaves when passed a dualvar.
Another situation where it's completely OK to use !!
is when casting certain values to Boolean, for example in a return statement. Something like:
# Returns true or false.
sub has_item {
my $self = shift;
return !!$self->{blessed_ref};
}
Most programmers should know the !!
idiom from statically typed languages where it's often used as a cast to bool. If that's what you want to do and there's no danger of unwanted side-effects, go with the double negation.
Perhaps it's not so bad for code only you see.
However, your fellow developer might see it one day fixing a bug in your code, and stumbles upon this. There are two options
- He thinks you made a mistake and removes one
!
, invalidating your code - He thinks this is just software rot, and removes both
!!
without a second thought. After refactoring some other code, it suddenly hangs on... a type mismatch?
Either way, it will waste the time of future developers, unless they happen to be familiar with it. Intelligible code is never for yourself (well, it could help), but for any others who will ever see your code.