tags:

views:

261

answers:

5

This is strange. The following:

$sum = !0;
print $sum;

prints out 1 as you would expect. But this

$sum = !1;
print $sum;

prints out nothing. Why?

+3  A: 

The ! operator does boolean operations. "" (The empty string) is just as false as 0 is. 1 is a convenient true value. ! shouldn't be relied on to do anything other than produce some true/false value. Relying on the exact value beyond that is dangerous and may change between versions of Perl.

Matthew Scharley
It certainly won't change between versions of Perl5.
ysth
One would assume so, but anything is possible. Besides, I know people who are still using PHP 4 till very recently. It's entirely possible that a script written now will last through Perl 6, and possibly even further.
Matthew Scharley
Very few perl 5 scripts will run in perl 6 without modification; it's a different language (while still being Perl)
ysth
+27  A: 

Be careful: what you've written isn't doing what you think it's doing. Remember, perl has no real boolean datatype. It's got scalars, hashes, lists, and references. The way it handles true/false values, then, is contextual. Everything evaluates to "true" in perl except for undefined variables, the empty list, the empty string, and the number 0.

What your code is doing, then, is taking the inverse of a value that evaluates to "false", which can be anything which is not in the list above. By convention and for simplicity's sake, perl returns 1 (though you should not rely on that; it could very well return a list containing a series of random numbers, because that will evaluate to "true" as well.)

A similar thing happens when you ask for the inverse of a value that evaluates to "true." What's actually being printed out is not "nothing," it's the empty string (''), which, as I mentioned, evaluates to "false" in boolean expressions. You can check this:

print "This evaluates to false\n" if( (!1) eq '');

If you're asking for why perl spits out the empty string instead of one of the other "false" values, well, it's probably because perl is made to handle strings and that's a perfectly reasonable string to hand back.

Omar Zakaria
Makes sense, thanks. I was trying to implement a half-adder in Perl just for fun, so I'll try to find a way around it.
Goose Bumper
Very well written.
Axeman
@aditya see http://stackoverflow.com/questions/1134962/weird-issue-with-booleans-in-perl/1135029#1135029 for a workaround.
Sinan Ünür
@sinan – the best workaround I found was this: I simply added zero to the result. This forced the value to behave as a int instead of a string.
Goose Bumper
@aditya: works the same way if you OR in 0: $possibly_undef || 0
Axeman
There is another false value, the string containing only a "0".
Svante
It's not just an empty string, it's a dual variable with 0/''.
daotoad
+8  A: 

The operators that only return a boolean result will always return 1 for true and a special false value that's "" in string contexts but 0 in numeric contexts.

ysth
+2  A: 

See perldoc perlsyn:

Truth and Falsehood

The number 0, the strings '0' and '' , the empty list () , and undef are all false in a boolean context. All other values are true. Negation of a true value by ! or not returns a special false value. When evaluated as a string it is treated as '' , but as a number, it is treated as 0.

There, if you print the value as a number, you will get 0 rather than the empty string:

printf "%d\n", $_ for map { !$_ } (1, 0);

or

print 0 + $_, "\n" for map { !$_ } (1, 0);

Compare those to

printf "%s\n", $_ for map { !$_ } (1, 0);

and

print $_, "\n" for map { !$_ } (1, 0);
Sinan Ünür
+1  A: 

Here's an addendum to the other great answers you've already gotten.

Not's Not Not

Consider the following code that tests each of Perl's 'not' operators:

!/usr/bin/perl

use strict; use warnings;

for( '!1', 'not 1', '~0' ) { my $value = eval; my $zero_plus = 0 + $value;

print join "\n", 
    "\nExpression: $_",
    "Value:     '$value'",
    "Defined:   " . defined $value,
    "Length:    " . length($value),
    "Plus:      " . +$value,
    "Plus Zero: '$zero_plus'",
    '';

}

print "\nTest addition for a literal null string: "; print 0+'', "\n";

use Scalar::Util qw(dualvar);

{ # Test a dualvar my $value = dualvar 0, ''; my $zero_plus = 0+$value;

print join "\n", 
    "\nExpression: dualvar",
    "Value:     '$value'",
    "Defined:   " . defined $value,
    "Length:    " . length($value),
    "Plus:      " . +$value,
    "Plus Zero: '$zero_plus'",
    '';

}

Executing it results follow. Notice the warning message:

Argument "" isn't numeric in addition (+) at test.pl line 21.

Expression: !1
Value:     ''
Defined:   1
Length:    0
Plus:
Plus Zero: '0'

Expression: not 1
Value:     ''
Defined:   1
Length:    0
Plus:
Plus Zero: '0'

Expression: ~0
Value:     '4294967295'
Defined:   1
Length:    10
Plus:      4294967295
Plus Zero: '4294967295'

Test addition for a literal null string:  0

Expression: dualvar
Value:     ''
Defined:   1
Length:    0
Plus:
Plus Zero: '0'

From this we learn several things.

The first two items are not all that exciting:

  • !1 and not 1 behave in basically the same way.
  • Unsurpisingly, ~1 is different (it's the bitwise not).

Now, the interesting item:

  • While we do get a warning for line 21 (0+''), there is no warning generated when we add 0+!1.

It Takes Two to Tangle

Something fishy is happening, and that fishiness has to do with special scalar contexts in Perl. In this case, the distinction between numeric and string contexts. And the ability to create a variable that has different values in each context, aka a dual variable.

It looks like !1 returns a dual variable that returns 0 in numeric context and null string in string context.

The dualvar test at the end, shows that a homemade dualvar works the same way as !1.

But It's A Good Thing

Like many Perl features, dual variables seem at first to defy expectations, and can be confusing. However, like those other features, used appropriately they make life much easier.

As far as I know, a dualvar of 0 and '' is the only defined value that will return false in all scalar contexts. So it is a very sensible return value for !1. One could argue that undef is a good false result, but then an unitialized variable is not distinguishable from a false value. Also, attempts to print or add the results of booleans would then be plagued with unnecessary warnings.

Another famous dualvar is $! or $OS_ERROR if you use English. In numeric form, you get the error code, in string form, the error code is translated for you.

It's Not Nothing, Its Empty, but its Nought

So in summary, you aren't getting nothing, you aren't getting an empty string, and you aren't getting zero.

You are getting a variable that is both an empty string and 0 at the same time.

daotoad