Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

  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) 
like image 514
Flimzy Avatar asked Oct 08 '15 11:10

Flimzy


2 Answers

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
like image 99
Sobrique Avatar answered Oct 23 '22 12:10

Sobrique


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.

like image 29
David Hammen Avatar answered Oct 23 '22 12:10

David Hammen