Why is "!!" considered bad form in Perl?

PerlBooleanType Conversion

Perl Problem Overview


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.

  1. 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;
     }
    
  2. Using a boolean value as a hash key (clearly a simplified example):

     sub foo {
         my ($input) = @_;
         my %responses = ( '' => "False", 1 => "True" );
         return $responses{ !! $input };
     }
    
  3. 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;
     }
    
  4. 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)
    

Perl Solutions


Solution 1 - Perl

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 chomped 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 is 1, !!0 is dualvar('', 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

Solution 2 - Perl

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.

Solution 3 - Perl

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:

  1. Use something like $count++ if $condition.

  2. Truth values as hash keys? That's simply evil. At least, it's surprising for anyone who has to maintain your code.

  3. Here the use of !! is justified.

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

Solution 4 - Perl

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

  1. He thinks you made a mistake and removes one !, invalidating your code
  2. 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.

Solution 5 - Perl

Depending on the job, it's bad form for a lot of business and interview reasons as well...

  1. when working on a team project, or any other code where many other developers could be working on it, don't obfuscate (or follow company policy)
  2. going of what sanchises said, it will cause more work, and does not clearly display the intentions of the code.
  3. it's simply unnecessary, and as discussed above - it sounds like there is at least one scenario where the result would not work - hence a bug.
  4. showing uses of obscure coding in an interview, when there is an easier and clearer way to do it doesn't look favorably in an interview.
  5. an interview is the place where you want to show that you are a team player, not that you are going to whip out weird crap that nobody else understands (unless it happens to be the answer to an unsolved issue of course)

p.s. it would have been in good form to ask the interviewer why he considered it "bad form". A handy interview followup tip, would be to send him a thank you and ask him why he considers it "bad form"

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFlimzyView Question on Stackoverflow
Solution 1 - PerlSobriqueView Answer on Stackoverflow
Solution 2 - PerlDavid HammenView Answer on Stackoverflow
Solution 3 - PerlnwellnhofView Answer on Stackoverflow
Solution 4 - PerlSanchisesView Answer on Stackoverflow
Solution 5 - PerljonView Answer on Stackoverflow