boleary has asked for the wisdom of the Perl Monks concerning the following question:

All to often I find myself struggling with the way Perl treats 0 as a character/string

I find that when I have a string that is just "0" instead of using:

 if($string)

I need to use

if (defined($string)) or if(length($string))

And no matter how many times I do data file processing where '0' is a valid entry, I always have to come back and put in one of those fixes to properly deal with the '0'

am I an idiot or is this a common issue that I should be handling differently?

Replies are listed 'Best First'.
Re: methods for dealing with zero '0' as a string or char (updated)
by AnomalousMonk (Archbishop) on Aug 02, 2019 at 13:39 UTC

    This is a common issue that you should be handling pretty much as you have shown, depending on circumstances. Please see Truth and Falsehood | the first paragraph in Declarations (kinda) or better yet, What is true and false in Perl? (update: or the link (third paragraph) in Eily's post).

    Update 1: "Truth and Falsehood" has absquatulated, so had to fix some links.

    Update 2:

    I find that when I have a string that is just "0" ...
    ...
    I need to use
     if (defined($string)) ...
    Just that case might arise if you're reading lines/records from a filehandle with readline($filehandle), e.g., if the last line of the file is "0" and is not newline-terminated (i.e., is just "0"/false and not "0\n"/true). That's why the Perl compiler will "optimise" (if that's the right term) a while-loop condition expression like
        while (my $line = <$fh>) { ... }
    to
        while (defined(my $line = <$fh>)) { ... }
    c:\@Work\Perl\monks>perl -wMstrict -MO=Deparse,-p -le "my $filename = 'foo'; open my $fh, '<', $filename or die qq{opening '$filename': $!}; ;; while (my $line = <$fh>) { print $line; } " BEGIN { $^W = 1; } BEGIN { $/ = "\n"; $\ = "\n"; } use strict 'refs'; (my $filename = 'foo'); (open(my $fh, '<', $filename) or die("opening '${filename}': $!")); while (defined((my $line = <$fh>))) { do { print($line) }; } -e syntax OK
    See O and B::Deparse. Compiled under Perl version 5.8.9.


    Give a man a fish:  <%-{-{-{-<

      I already /msg'd AnomalousMonk about it but maybe a post is a better idea: v5.26 had a section on Truth and Falsehood but not v5.28. I suppose this information is still supposed to be found somewhere in the documentation, but I have no idea where...

      Edit: it is in perldata

        The change that moved the section was 77fae4394, resulting from #115650. (It's a bit of an inconvenience since I've linked to that section quite a few times...)

        Update: I have since posted Truth and Falsehood as a replacement.

      thanks for the background!

Re: methods for dealing with zero '0' as a string or char
by Laurent_R (Canon) on Aug 02, 2019 at 14:18 UTC
    Depending on your use case, Perl has a special string, '0 but true', which is evaluated to 0 in numeric context, but as true in Boolean context. This is sometimes useful for returning a zero but true value from a function to the caller. See for example https://riptutorial.com/perl/topic/649/true-and-false. The only thing special about that string is that it will issue no warning in numeric context; otherwise it is true, as any string other than the string containing only 0 ('0') and the empty string (''). Another possibility in the same context is to return a string such as '0e0'.

    Another possibility is sometimes to return a reference to the variable containing the "0" string.

      Thanks, I like this trick

Re: methods for dealing with zero '0' as a string or char
by LanX (Saint) on Aug 02, 2019 at 15:05 UTC
    > am I an idiot or is this a common issue that I should be handling differently?

    No you are not an idiot, it's just that different people have different understandings about a true scalar.

    This means you need to be explicit what you exactly want to test.

    • if (defined($string)) tells me that I didn't get undef
    • if(length($string)) is testing against empty strings (NB: not the same like undef !!!)
    • if($scalar) is testing against Perl's concept of truthiness.

    Background: Perl is designed such that scalars are transparent to their C type, i.e. no matter if the 0 is internally an integer, float or string. °

    Hence the inner representation of a scalar can change without effecting the boolean context.

    NB: If you really need an explicit string type which is only false when empty, you are free to create your own "string" object and overload it's bool behaviour.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

    update

    °) in other languages with explicit types you are forced to transform a number inside a string into an int or float before using numeric operations.

    For instance in Python

    • a + b can be a string concat or a numeric addition depending on the type, in Perl it's always only an addition.
    • and 3/2 will yield 1 because it's an integer operation.

      Just as an adjunct to what you have there, length returns FALSE for a zero-length EXPR and also an undefined EXPR. The latter behaviour was introduced in 5.12.0 (perl5120delta: Other potentially incompatible changes).

      Interestingly, given the general discussion here, the FALSE value returned for a zero-length EXPR is 0, while the FALSE value returned for an undefined EXPR is undef.

      — Ken

        for clarification

        > Interestingly, given the general discussion here, the FALSE value returned for a zero-length EXPR is 0, while the FALSE value returned for an undefined EXPR is undef.

        Some operators in Perl return an internal magic boolean value, which is !!0 for FALSE and !!1 for TRUE.

        length is not one of them, it returns values which are interpreted as FALSE or TRUE, returning undef for undef is very consistent in my eyes.

        Though one probably could argue that warnings uninitialized should trow a warning here, I'm a bit confused in this matter.

        I remember that tobyink started a discussion once that warnings uninitialized leads to inconsistent behaviour.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: methods for dealing with zero '0' as a string or char
by Don Coyote (Hermit) on Aug 03, 2019 at 17:17 UTC

    hello boleary

    Just reiterating what has already been said, but have spent most of the day getting there.

    I have been getting a bit deeper lately, and am just posting what is hopefully useful. If you value your sanity, you'll read just enough of Devel::Peek Examples to understand SV, IV, the difference between PVIV and PVNV and then get the hell out of there.

    I'll save some trouble. On the top lines SV = means a scalar, which all of these are.

    SV = PV means its a String
    SV = IV means its an Integer

    You can easily see the PV part that describes when the scalar values are strings, and they all have a trailing null, the backslash followed by a zero.

    When the scalar values are Integers the IV part on the bottom line shows the value, that is not nul terminated.

    #!perl use strict; use warnings; use feature qw/say/; use Devel::Peek; my @trailing_truth = ( "0", "0\n", 0, '0', '0'."\n" ); foreach my $zeroe ( @trailing_truth ){ warn $zeroe ? "true\n" : "false\n"; Dump $zeroe; # Dump defined $zeroe; # check the REFCNT! # Dump length $zeroe; # # Dump 0+$zeroe; # numeric coercion # Dump !!$zeroe; # boolean coercion # say "[][][][][][][][][][]\n"; print "\n"; }

    output

    false SV = PV(0x1e850c) at 0x1ea53c REFCNT = 2 FLAGS = (POK,pPOK) PV = 0x26b37bc "0"\0 CUR = 1 LEN = 12 true SV = PV(0x1e851c) at 0x1ea5cc REFCNT = 2 FLAGS = (POK,pPOK) PV = 0x26b381c "0\n"\0 CUR = 2 LEN = 12 false SV = IV(0x1ea618) at 0x1ea61c REFCNT = 2 FLAGS = (IOK,pIOK) IV = 0 false SV = PV(0x1e8534) at 0x1ea64c REFCNT = 2 FLAGS = (POK,pPOK) PV = 0x26b39dc "0"\0 CUR = 1 LEN = 12 true SV = PV(0x1e854c) at 0x1ea66c REFCNT = 2 FLAGS = (POK,pPOK) PV = 0x26b39fc "0\n"\0 CUR = 2 LEN = 12 Press any key to continue . . .

    PVNV means its a double or float, this comes up on the return value from the defined builtin. The return value also shows that the scalar has both numeric and string values stored now.

    The trick on this one is putting the conditional test before the Devel::Peek::Dump on there to help see what test the values relate to.