Hi

I started to write a test script for "Boolean Operators" with and w/o "Short-Circuit" in Perl.

And quickly found me needing to define and test "Truthiness" in Perl.

(Wait ... "0 but true" is false? Remembered it differently ;-)

And now I find myself obliged to also define "Contexts", because an empty list is also false.

And "Data Types", because internally it's more difficult, than just Scalar, Array and Hash.

It's a lot of work, but in the end it could help in many corners when done correctly:

Properly done means that it needs to be axiomatic, i.e. higher level tests need to only use features proven in lower levels.

Like so often, after a first success I find myself a bit stuck in the big picture.

I'll throw in my first approach as is for meditation.

There is a lot to be criticized, but my normal perfectionism is too risky and might lead to a never release cycle.

Thoughts?

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

use strict; use warnings; use Data::Dump qw/pp dd/; use Scalar::Util qw/isdual/; use Test::More; =head1 Boolean Operations in Perl =head2 True and False values Perl has no boolean datatype but a "boolean context" where other values evaluate to true or false. Operands can be literal values like depending on context. - strings - numbers Lists are converted to scalars Perl variables have 3 main datatypes: Scalars, Arrays and Hashes. The boolean context is just a specialized scalar context and the literal empty list C<()> evaluates to false. Arrays and Hashes are false iff they are empty. Scalar values are internally represented - strings - integers - float (double precission) - references/objects - undefined But Perl allows to use them all interchangabliy by casting them automa +tically. 1. Literal "nothing" is always false - C<undef> in scalar context - empty list, either implicit C<return;> or explicit C<()> NB: a non-empty list is always true, even if the elements are false like in C<return undef> 2. The neutral element of every internal datatype is FALSE: - i.e. all literal zeroes for numbers 0, 0.0, 0e0 - the empty string '' - empty arrays and hashes NB: The "0" string is also false, because Perl treats 0 and "0" to be the same scalar. But "0.0" or "0E0" are never false, these are literal notations. 3. References including those blessed to objects are always true. NB: You can treach with C<use overload> to make objects false. =cut =head2 Boolean return values Most Boolean operators like C<!> for C<not> return a "default scalar" as represntative for true and false. These can be easily construted by double negation false := !!0 true := !!1 But so called "Short-circuit Operators" change the control-flow. They return the last evaluated side instead, which is guarantied to be logically correct (later more) =cut my @false = ( !!0, # default 0, 0.0, 0e0, 0e10, "0", # scalar zeroes '', # empty string undef, # undef !!(), # empty list ); # NB: you can fake "false" objects by using =overload= sub is_FALSE { return unless isdual($_[0]) ; my $num = $_[0]; my $str = $_[0]; return ($num == 0) && ($str eq ""); } cmp_ok ( !!0, "==", 0, "!!0 is 0 in numeric context"); cmp_ok ( !!0, "eq", "", "!!0 is '' in string context"); ok ( is_FALSE(!!0)); # anything else is true my @true = ( !!1, # default 1 , "A", "0abc", "0 but true", "0e0", # all strings except "0" \0, [], {}, # refs (including blessed obje +cts) ); cmp_ok ( !!1, "==", 1, "!!1 == 1 in numeric context"); cmp_ok ( !!1, "eq", 1, "!!1 eq 1 in string context"); cmp_ok ( !!1, "eq", '1', "!!1 eq '1' in string context"); for my $x ( @true ) { ok ( $x, "true: ". pp($x) ); } for my $x ( @false ) { ok ( ! $x, "false: ". pp($x) ); } #done_testing(); #exit; =head2 short-circuit operators =cut my @values = (@false, @true); warn pp \@values; for my $x ( @values ) { for my $y ( @values ) { is( ($x || $y), ($x ? $x : $y), "|| " .pp($x,$y) . " => " . pp ($x || $y) ); } } for my $x ( @values ) { for my $y ( @values ) { is( ($x && $y), (!$x ? $x : $y), "&& ".pp($x,$y) . " => " . pp ($x && $y) ); } } for my $x ( @values ) { for my $y ( @values ) { is( ($x // $y), ( (defined $x) ? $x : $y), "// ".pp($x,$y) . " => " . pp ($x && $y) ); } } done_testing();

Replies are listed 'Best First'.
Re: Defining, Testing and Documenting Perl?
by Tux (Canon) on Dec 23, 2019 at 20:37 UTC

    /me mumbles .oO( ties and overloads )


    Enjoy, Have FUN! H.Merijn

      Fun fact: the Moose "Bool" datatype will accept overloaded objects, but only if they stringify to "", "0", "1" or undef. It doesn't care what they boolify to!

      Other fun fact: the Mouse "Bool" datatype will accept overloaded objects, but only if they boolify to a false value.

       

      This is why Types::Standard 1.004 abandoned trying to be compatible with Moose and Mouse's definitions of "Bool" and instead just accepts "", "0", "1" and undef, but offers a coercion from other values.

      > mumbles .oO( ties and overloads )

      what's your point?

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

        I believe a tied hash can override how it behaves in scalar context, so that an empty hash can return true and a non-empty hash can return false.

        An overloaded object can do various tricky things with regards to how it behaves as a boolean. For example, it could die.

        if ($x || $y) { say "Something was true. What was it?"; say '$x' if $x; say '$y' if $y; } else { say "Neither was true."; }

        It is possible for this to print "Something was true?" but not tell you what was true. This is because $x or $y could be an overloaded object returning true or false at random each time they are checked.

A reply falls below the community's threshold of quality. You may see it by logging in.