Why is "!!" considered bad form in Perl?
Asked Answered
E

7

58

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)
    
Eserine answered 8/10, 2015 at 11:9 Comment(10)
It might end up being opinion based and thus OT. After all - code idioms you like/dislike are opinion, and code styles are more policy. However - I've given a reason I would call it 'bad form', and think as a collection of possible reasons why it might (or might not) be, that's not so bad.Cantankerous
sub Bool { shift ? 1 : "" } should be same as sub Bool { !!shift }Northman
What's the alternative? Even the ternary is a better alternative; it at least says what the outcome will be: sub doublebang($){return $_[0]?1:''} .Contravention
Still having a bit of trouble with that last example. I'm really having difficult with the notion of quite why you'd use a boolean state, and then do arithmetic operations with it.Cantankerous
@Сухой27, It's not the same. sub Bool { shift ? 1 : "" } is not the same as sub Bool { !!shift }. Try Bool(0)+0 when warnings are on.Graphic
Many of those "operators" are not suitable for production code, because they are obscure to the uninitiated -- perlsecretDebbidebbie
Ok, to keep them internally marked as PVNV, !0 and !1 should be used.Northman
Bang bang operator. I love it. I will forever call it this.Silurid
While I'm not fond of using treating booleans as numbers (as in your example), I wouldn't have a problem using !!f() to normalize a boolean value (that could be a long string) into a simple value (except to question whether it's actually needed at all).Graphic
I don't know why my comment here was deleted.. Please help me out if there is a reason. But it seems neccessary to state that all of these answers are out of date with Perl 5.36+ which even if you had !! adopted that for the literal syntax for boolean type construction. See my answer below for more information.Ministry
C
38

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.

Contravention answered 8/10, 2015 at 12:50 Comment(1)
Agree about using post-if, but a ternary operator is not "obscure" nor obtuseEmigrant
C
65

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
Cantankerous answered 8/10, 2015 at 11:14 Comment(8)
I have to say - I can't think of any scenarios where I've needed an "actual boolean". Evaluating the truthiness of a scalar has always done the trick. The odd edge case of tri-state true/false/undef being perhaps the only exception, and there you've got defined and //.Cantankerous
OK. Sorry, going to need you to expand on "adding two boolean values". I mean, I've seen it used in the context of a boolean or operation. (As C/C++ does). But you've already got boolean or (and || for logical and | for bitwise) (Or xor for logical or ^ for bitwise)Cantankerous
@Flimzy - If you want to convert some value to 0 or 1, your !! trick doesn't do the trick. What !! does is to convert a truthy value to 1, a non-truthy value to the empty string. While 1 is the minimal truthy value in perl, 0 is not the minimal non-truthy value in perl. That would go to the empty string. Your !! trick doesn't do what you think it does.Contravention
The very last paragraph might be the most important one.Graphic
" You don't need to boolean convert in Perl." -- One obvious reason for returning a proper boolean is that you might not want to leak implementation details in a public API.Pironi
Whilst I agree generally, I'm also pretty sure that 'just' injecting !!$var into your API output is good form either.Cantankerous
@Sobrique: So what alternative do you advocate?Eserine
Conditionally returning an explicit value. Your API is going to have to apply a transform anyway - stringification if not conversion to JSON/XML or similar.Cantankerous
C
38

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.

Contravention answered 8/10, 2015 at 12:50 Comment(1)
Agree about using post-if, but a ternary operator is not "obscure" nor obtuseEmigrant
A
19

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.

Avert answered 8/10, 2015 at 15:57 Comment(6)
Ah yes, I forgot about the return case you mention at the end. That's another one I have used before.Eserine
For that last case, I would be thinking of simply using "defined" so you aren't tripped over by present but false values.Cantankerous
@Sobrique: unless you want to treat defined but false values as false.Eserine
Then you can just return the value, because any conditional you're testing it in... works exactly the same.Cantankerous
@Sobrique: It's generally considered bad form to return a data type other than that which is expected by the caller. If the function tests truthiness, it should return a boolean. Especially if you consider the "public API" argument made in other comments above. I don't think "don't convert to boolean" is a valid answer to this question.Eserine
If we were talking about a strongly typed language, I would agree. Perl isn't.Cantankerous
M
6

2024 UPDATE: CORE Bool Value Constructors

This is certainly not axiomatically true in modern Perl

The operator !! will always double-negate and evaluate in bool context. However, it's not true that the now-boolean value constructors !!1 evaluates to 1 anymore and hasn't been true in years, since version 5.36 (released in 2022). And wasn't really true in 2021 either,

The "true" and "false" boolean values, often accessed by constructions like !!0 and !!1, as well as being returned from many core functions and operators, now remember their boolean nature even through assignment into variables. The new function is_bool() in builtin can check whether a value has boolean nature.

This is likely to be useful when interoperating with other languages or data-type serialisation, among other places.

  • 1 is an sv_iv in perl. An integer.
  • !!1 is a constructor for sv_yes.
  • !! constructs sv_yes or sv_no depending on the argument which is evaluated in bool context.
  • builtin::is_bool (internally SvIsBOOL) will return true on sv_yes and sv_no, otherwise false.

It was previously the case that !!1 stored as an sv_iv which in bool context would eval true. But now Perl has bools distinct from IVs. Likewise, !!0 is not an empty string. It's sv_no which in string context evaluates to an empty string. But it's not the same value at all. ;)

All that to say, when you're in Perl the right way to handle bools is to use:

  • The true value constructor builtin::true or !!1
  • The false value constructor builtin::false or !!0

This replaces the need for things like Types::Serialiser which previously provided an object that was overloaded to evaluate to true and false.

You may be asking why this distinction is need or desired between 1 and true, and 0 and false when used in Perl. The reason is simple, because you want to later take that true and false, and send it out to a JSON or TOML serializer as true and false which is consumed by a language which does support bool primitives. Perl was pretty unique in not having a bool primitive, and it bothers the world when we send out 1 and 0 instead. This is a way to create a lasting distinction between the integers and bools, while not losing any functionality.

Note while the syntax !!0 and !!1 are heavily used now tested and supported by core perl, !!expr is also valid and commonplace. It also has the effects of creating a lasting boolean value (internal sv_yes and sv_no), and that distinction is just as valuable as on the simple literals !!0 and !!1

Ministry answered 17/6, 2024 at 22:58 Comment(0)
F
5

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.

Fester answered 8/10, 2015 at 21:42 Comment(2)
In college days I gave my friend a hard time for nesting 2 level deep ternary operators in one line and calling it "compact and cool". I said "it is stupid and impossible to maintain because I have to spend 30 minutes to manually trace something."Cassady
As wise man once said. Debugging is twice as hard as coding. If you are as clever as you can be when you write it, how will you ever debug it?Cantankerous
T
2

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"

Treadmill answered 14/10, 2015 at 17:27 Comment(1)
+1 for following up to show the interviewer you can have a reasonable discussion about it and respect their opinions.Mimosaceous
F
-2

one of my most frequently deployed use case for !! x would be upfront special handler for 0 or 1 after accepting numeric input

e.g. in a pow() type of subroutine, there are definite time savings for special handling of both the base to be exponentiated, and also the exponent itself -


  • exponent == 0 := directly return numeric 1

  • exponent == 1 := directly return the base


  • base == 1 := just directly return the base of numeric 1

  • base == 0 :=

    exponent < 0 —> return +inf (some say -inf)

    ( default ) —> return a 1/0 indicator of whether exponent is also zero


So instead of having to check base == 0 or base == 1, I'd do something like :

 if ( base == !! base ) { 

    . . . 

 } else if ( expn == !! expn ) { 

    . . . 
 }

Since only numeric 0 or 1 could get themselves back after a double logical negate.

I suppose one could also do if ( base == base * base ) to capture 0 or 1 without using something like a case/switch statement, but that could be much slower if we're dealing with gigantic bigints

Figured answered 3/3, 2024 at 10:38 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.